Search code examples
pythondjangoformsinline-formset

Django Inline formsets only saving three objects


I have a question object that has a foreign key field to a discussion object. I am trying to render a form using a discussion ModelForm, and a question Inline formset. When it renders an empty form to the page, everything works fine, and all the fields are where they need to be. The error is that I have JQuery that allows the user to add more questions if they want by appending html exactly identical to the other question inputs and labels with appropriate name values. It originally just puts three empty text areas for the question. In my view function, I save both the discussion Model form, and the Inline form with the request.POST data. I can see in the POST, that all the data, even the added questions are there, but when I redirect them to a page to see a read only version of the new discussion object and its related fields, there are always only three questions, instead of 4+.

view

def new_discussion(request):
    member = get_member(request)

if request.method == 'POST':
    form = DiscussionForm(data=request.POST)
    QuestionInlineFormSet = inlineformset_factory(Discussion, Question, fields='__all__')

    if form.is_valid():

       created_discussion = form.save(commit=False)
       formset = QuestionInlineFormSet(request.POST, instance=created_discussion)
       if formset.is_valid():
            created_discussion.community = member.community
            created_discussion.save()
            formset.save()

            return HttpResponseRedirect('/user/discussions/')

template

{% extends 'member/discussions.html' %}

{% block extra-head %}
    <script src='//cdn.tinymce.com/4/tinymce.min.js'></script>
    <script>

    var count = {{ questions|length }};

    $(document).ready(function(){
      $('#add_question').on('click', function(e){
            e.preventDefault()
            console.log(count)

            var form = $('.form-data')
            form.append('<div class="col-xs-12 extra-' + count + ' form-group"></div>')
            $('#question-count').val(count)

            var extra = $('.extra-' + count)
            extra.append('<label for="id_question_set-' + count + '-question">Question:</label>')
            extra.append('<textarea cols="40" id="id_question_set-' + count + '-question" maxlength="200" name="question_set-' + count + '-question" rows="10"></textarea>')
            extra.append('<label for="id_question_set-' + count + '-scripture">Scripture:</label>')
            extra.append('<input id="id_question_set-' + count + '-scripture" maxlength="30" name="question_set-' + count + '-scripture" type="text">')
            extra.append('<label for="id_question_set-' + count + '-display_order">Display order:</label>')
            extra.append('<input id="id_question_set-' + count + '-display_order" name="question_set-' + count + '-display_order" type="number" value="1">')
            extra.append('<label for="id_question_set-' + count + '-DELETE">Delete:</label>')
            extra.append('<input id="id_question_set-' + count + '-DELETE" name="question_set-' + count + '-DELETE" type="checkbox">')
            extra.append('<input id="id_question_set-' + count + '-discussion" name="question_set-' + count + '-discussion" type="hidden">')
            extra.append('<input id="id_question_set-' + count + '-id" name="question_set-' + count + '-id" type="hidden">')
            count += 1
        })
    })

    tinymce.init({
        selector: '#id_content',
        plugins : 'media code',
        media_alt_source: true,
        media_poster: false,
        media_dimensions: false
      });

</script>
{% endblock %}

{% block discussion %}

<form method='POST' id='discussion-form'>

    <div class="form-data">

        {% csrf_token %}

        <div class='col-xs-12 form-group'>
            <label for='id_title'>Title</label>
            {{ form.title }}
        </div>

        <div class='col-xs-12 form-group'>
            <label for='mytextarea'>Content</label>
            {{ form.content }}
        </div>

        <div class='col-xs-12 form-group'>
            <label for='id_audio'>Audio</label>
            {{ form.audio }}
        </div>

        <input type="hidden" id="question-count" name="question-count" value='0'>

        <div class="col-xs-12">
            {{ questions }}
        </div>

    </div>

    <button class="btn btn-info" style="margin-left: 15px; margin-bottom: 10px;" id="add_question">Add Question</button>

    <br>

    <div class='btn-wrapper col-xs-12'>

        <input id='save' type='submit' class='btn btn-primary' style="margin-right: 10px;" value='Save' />

        {% if view %}
            <a class='btn btn-primary' style="margin-right: 10px;" href='/user/discussions/edit/{{ discussion.id }}/preview/'>
            View
        </a>
        {% endif %}

        <!-- <input id='preview' type='submit' class='btn btn-primary' style="margin-right: 20px;" value='Preview' /> -->
        <a href="/user/dashboard" class='btn btn-warning'>Back to dashboard</a>

    </div>

</form>

{% block discussion_nav %}

{% endblock %}

{% endblock %}

Solution

  • You need to update the hidden field from the management form which specifies the total number of forms. It needs to be updated each time you add a form. You can target this using the id:

    id_form-TOTAL_FORMS
    

    Currently, your count variable is assigned on page load (which seems to be 3 forms). Django is validating your formset to only accept the number of forms specified, hence dropping all of your other formset forms. So each time you add a form, you need to increment this by 1.

    Here's some info on the management form: https://docs.djangoproject.com/en/dev/topics/forms/formsets/#understanding-the-managementform