4

I'm trying to create a survey page using Django that has the functionality to add questions and choices(for each question) dynamically. I have three following model classes: Survey, Question and Choice.

I did a lot of research to figure out how to go about implementing the forms and it seems nested formsets using inline_formsets is the right way to go(I used this tutorial). However, I can't figure out how to add questions and choices dynamically with nested formsets.The JavaScript library that I'm trying to use(This library) does not have an example for nested formsets and I'm not sure if it supports it. This code is what i have so far:

forms.py

from django.forms import BaseInlineFormSet
from django.forms import HiddenInput
from django.forms import inlineformset_factory
from django.forms import ModelForm

from .models import *


class SurveyForm(ModelForm):
    class Meta:
        model = Survey
        fields = [
            'name',
            'description',
        ]


class BaseQuestionFormset(BaseInlineFormSet):

    def add_fields(self, form, index):
        super(BaseQuestionFormset, self).add_fields(form, index)
        form.nested = QuestionChoiceFormset(
            instance=form.instance,
            data=form.data if form.is_bound else None,
            files=form.files if form.is_bound else None)

    def is_valid(self):
        result = super(BaseQuestionFormset, self).is_valid()
        print(result)
        if self.is_bound:
            for form in self.forms:
                if hasattr(form, 'nested'):
                    result = result and form.nested.is_valid()

        return result

    def save(self, commit=True):
        for form in self.forms:
            form.save(commit=commit)
        result = super(BaseQuestionFormset, self).save(commit=commit)

        for form in self.forms:
            if hasattr(form, 'nested'):
                if not self._should_delete_form(form):
                    form.nested.save(commit=commit)

        return result


QuestionFormset = inlineformset_factory(
    parent_model=Survey, model=Question, fields='__all__',
    formset=BaseQuestionFormset, extra=1)
QuestionChoiceFormset = inlineformset_factory(
    parent_model=Question, model=Choice,
    fields='__all__', extra=1)

views.py

@login_required
def create(request):
    survey = Survey()
    if request.method == 'POST':
        survey_form = SurveyForm(request.POST, instance=survey)
        question_formset = QuestionFormset(
            request.POST, prefix='questions', instance=survey)

        if survey_form.is_valid() and question_formset.is_valid():
            survey_form.save()
            question_formset.save()
            # url = '/preview/{}'.format(survey.pk)
            # return HttpResponseRedirect(url)
    else:
        survey_form = SurveyForm(instance=survey)
        question_formset = QuestionFormset(instance=survey, prefix='questions')

    context = {
        'survey_form': survey_form,
        'question_formset': question_formset,
    }

    return render(request, 'surveys/create.html', context)

create.html

{% extends 'surveys/base.html' %}
{% load static %}
{% block container %}
<div class="container">
  <h1> Create New Survey </h1>
  <form method="post">
    {% csrf_token %}
    {{ question_formset.management_form }}
    {{ question_formset.non_form_errors }}
    {{ survey_form.as_p }}
    <table id='myForm1'>
      {% for question_form in question_formset.forms %}
        {{ question_form }}

        {% if question_form.nested %}
          {{ question_form.nested.management_form }}
          {{ question_form.nested.non_form_errors }}
          <div id='myForm2'>
          {% for choice_form in question_form.nested.forms %}
            {{ choice_form }}
          {% endfor %}
          </div>
        {% endif %}
      {% endfor %}
    </table>

    <button type="save">Save</button>
  </form>
</div>
<script src="{% static "surveys/dist/js/jquery.js" %}"></script>
<script src="{% static "surveys/dist/js/jquery.formset.js" %}"></script>
<script type="text/javascript">
  $(function(){
    $('#myForm1').formset({
      prefix: '{{ question_formset.prefix }}',
      formCssClass: 'dynamic-question_formset',
      addText: 'add question'
    });
    $('#myForm2').formset({
      prefix: '{{ choice_form.prefix }}',
      formCssClass: 'dynamic-choice_form',
      addText: 'add choice'
    });
  })
</script>
{% endblock %}

3
  • how are the forms currently displaying? are nested questions all appearing before they shuold or not appearing at all? Commented Mar 18, 2018 at 21:40
  • I also dont get the fields = '__all__' argument, is it necessary? is it meant to grab __all__ from some sort of fields class? Commented Mar 18, 2018 at 22:06
  • The forms are displaying as expected. But if I try to add questions dynamically they won't save and I get the error: ['ManagementForm data is missing or has been tampered with'] and Django requires "fields" argument for inline_formset. 'all' gets all the fields from model class Commented Mar 18, 2018 at 23:07

1 Answer 1

4

I ended up using this plugin! It has explanation with examples for nested formsets.

Sign up to request clarification or add additional context in comments.

1 Comment

can you show how you used this library - code/GitHub etc? I have been trying to get the demo example to work (main form, inline form with nested form all on same page) and it just doesn't work - errors out. Also looked at this and couldn't get the demo to run either yergler.net/2009/09/27/nested-formsets-with-django

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.