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 %}
I ended up using this plugin! It has explanation with examples for nested formsets.