ehka
ehka

Reputation: 81

Django nested formsets adding forms dynamically

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 %}

Upvotes: 4

Views: 6348

Answers (1)

ehka
ehka

Reputation: 81

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

Upvotes: 4

Related Questions