Problem:
We have a page with identical repeating forms on a single page.For example:
- adding resources for an article
- adding an item in a to do list
- creating a simple questionnaire
Answer:
Django makes this easy for you with the use of formsets.Sample App:
Here is a simple questionnaire app which creates questions easily using the concept of formsets.Sample Models:
from django.db import models
class Questionnaire(models.Model):
name = models.CharField(max_length=100)
def __unicode__(self):
return self.name
class Question(models.Model):
question = models.TextField(
help_text="e.g. What is the meaning of life?"
)
questionnaire = models.ForeignKey(Questionnaire)
def __unicode__(self):
return str(self.questionnaire) + ' - ' self.question
Here we create the models for the Questionnaire App. Question has a many-to-one relationship with QuestionnaireSample Forms:
from django.forms import ModelForm
from questionnaire.models import Question, Questionnaire
class QuestionnaireForm(ModelForm):
class Meta:
model = Questionnaire
class QuestionForm(ModelForm):
class Meta:
model = Question
exclude = ('questionnaire',)
Here we create the forms for the Questionnaire App. We exclude the questionnaire field for the Question Form and add data for it behind the scenes.Sample Views:
from django.forms.formsets import formset_factory, BaseFormSet
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import render_to_response
from django.template import RequestContext
from questionnaire.forms import QuestionForm, QuestionnaireForm
def index(request, template='questionnaire/index.html'):
class RequiredFormSet(BaseFormSet):
def __init__(self, *args, **kwargs):
super(RequiredFormSet, self).__init__(*args, **kwargs)
for form in self.forms:
form.empty_permitted = False
QuestionFormSet = formset_factory(QuestionForm, formset=RequiredFormSet)
if request.method == 'POST':
questionnaire_form = QuestionnaireForm(request.POST)
question_formset = QuestionFormSet(request.POST, request.FILES)
if questionnaire_form.is_valid() and question_formset.is_valid():
questionnaire = questionnaire_form.save()
for question_form in question_formset.forms:
question = question_form.save(commit=False)
question.questionnaire = questionnaire
question.save()
return HttpResponseRedirect('success')
else:
questionnaire_form = QuestionnaireForm()
question_formset = QuestionFormSet()
context = {
'questionnaire_form': questionnaire_form,
'question_formset': question_formset
}
return render_to_response(template, context, RequestContext(request))
Here we create the views for the Questionnaire App. This is where we save the data. Initially, we create a blank Questionnaire form and blank Question forms handled by the QuestionFormSet. When the forms are submitted we have a handler which validates the Questionnaire form and Question formset, and ultimately saves the data once everything is good to go.Sample Template:
index.html
{% extends "base.html" %}
{% block title %}Questionnaire{% endblock %}
{% block extra_js %}
<script type="text/javascript">
//ADD FORM
function addForm(prefix){
var formCount = parseInt($('#id_'+prefix+'-TOTAL_FORMS').val());
var id_regex = new RegExp('(' + prefix + '-\\d+-)');
var replacement = prefix + '-' + formCount + '-';
//DUPLICATE FORM DIV
$('#id_'+prefix+'-TOTAL_FORMS').val(formCount + 1);
var new_formset = $('.'+prefix+'-div:last').clone(false);
new_formset.find('.formset_errors').html('');
new_formset.find('input').val('');
new_formset.find('.delete-form').show();
//REPLACE ID AND NAME
new_formset.children().each(function(){
if (this.id && this.name){
this.id = this.id.replace(id_regex, replacement);
this.name = this.name.replace(id_regex, replacement);
}
});
//INSERT AND ASSIGN REMOVE
new_formset.hide().insertAfter('.'+prefix+'-div:last');
new_formset.fadeIn('normal');
new_formset.find('.delete-form').click(function(e){
e.preventDefault();
RemoveItem(this, prefix);
});
}
//DELETE FORM
function deleteForm(btn, prefix){
var formCount = parseInt($('#id_'+prefix+'-TOTAL_FORMS').val()) - 1;
if(formCount == 0){
return;
}
//DELETE FORM
$('#id_'+prefix+'-TOTAL_FORMS').val(formCount);
$(btn).parent().fadeOut('fast', function(){
$(this).remove();
//UPDATE OTHER FORMS
var forms = $('.'+prefix+'-div');
for(var i = 0;i < formCount;i++){
var id_regex = new RegExp('(' + prefix + '-\\d+-)');
var replacement = prefix + '-' + i + '-';
$(forms.get(i)).children().each(function() {
if (this.id && this.name){
this.id = this.id.replace(id_regex, replacement);
this.name = this.name.replace(id_regex, replacement);
}
});
}
});
}
// Register the click event handlers
$("#add").click(function (e) {
e.preventDefault();
return addForm("form");
});
$(".delete").click(function (e) {
e.preventDefault();
return deleteForm(this, "form");
});
</script>
{% endblock %}
{% block main %}
<h1>Questionnaire App</h1>
<h2>Questionnaire</h2>
<form action="" method="POST">{% csrf_token %}
<div>
{{ questionnaire_form.as_p }}
</div>
<h2>Questions</h2>
{{ question_formset.management_form }}
{% for form in question_formset.forms %}
<div class="item">
{{ form.as_p }}
<p style=""><a class="delete" href="#">Delete</a></p>
</div>
{% endfor %}
<p><a id="add" href="#">Add another question</a></p>
<input type="submit" value=" Submit " />
</form>
{% endblock %}
Here we create the template which shows the Questionnaire Form and Question Formset. The javascript handles adding and deleting of Question Forms for the formset. So that's it. Here we have a sample app that dynamically adds questions to a questionnaire.
No comments:
Post a Comment