GeeS GeeS - 3 years ago 249
Python Question

Django survey with forms

I am building a survey tool and I'm wondering how would I continue with this or if my current solution is even the proper way to do it.


  1. Admin of the page may add or remove questions from questionnaires, so if I have understood it, I can't use ModelForms to handle the form data?

  2. A form may consist of 5 multichoice questions and 2 free text questions or any other amount of different types of questions so there isn't any fixed type of questionnaire

  3. How do I then save the values of the form as I do not have a model to use?



Is this even possible to achieve without using a model for the form?

Thank you for any input in advance.




views.py

from django.shortcuts import render
from .models import Questionnaire, Question, Answer

def index(request):
all_questionnaires = Questionnaire.objects.all()
all_questions = Question.objects.all()
return render(request, 'questions/index.html', locals())

def questionnaire_detail(request, questionnaire_id):
questionnaire = Questionnaire.objects.get(id=questionnaire_id)
questions = Question.objects.filter(questionnaire=questionnaire)
return render(request, 'questions/questionnaire.html',
{'questionnaire': questionnaire, 'questions': questions})

def calculate(request):
if request.method == 'POST':
pass


models.py

from django.db import models

MC = 'MC'
CN = 'CN'
TX = 'TX'

CATEGORY = (
(MC, 'Multichoice'),
(CN, 'Choose N'),
(TX, 'Text'),
)

VALYK = '1'
VALKA = '2'
VALKO = '3'
VALNE = '4'
VALVI = '5'

MULTICHOICE = (
(VALYK, 'Least'),
(VALKA, 'Less than average'),
(VALKO, 'Average'),
(VALNE, 'More than average'),
(VALVI, 'Most'),
)

class Questionnaire(models.Model):
questionnaire_name = models.CharField(max_length=100,
verbose_name="Questionnaire",
null=False,
default=None,
blank=False)
def __str__(self):
return self.questionnaire_name

class Question(models.Model):
questionnaire = models.ManyToManyField(Questionnaire)
question_text = models.CharField(max_length=200,
verbose_name="Questionnaire name",
null=True,
default=None,
blank=True)
question_category = models.CharField(max_length=2,
verbose_name="Question category",
null=False,
choices=CATEGORY,
default=None,
blank=False)
def __str__(self):
return self.question_text

class Answer(models.Model):
question = models.ForeignKey(Question)

class MultiChoiceAnswer(Answer):
answer = models.IntegerField(choices=MULTICHOICE)
def __str__(self):
return self.answer


questionnaire.html

{% extends "questions/base.html" %}
{% block title_html %}
Questionnaire
{% endblock %}
{% block h1 %}
Questionnaire
{% endblock %}
{% block content %}
{% if questions|length > 0 %}
<form action="{% url "questions:calculate" %}" method="post">
{% csrf_token %}
{% for question in questions %}
{{ question.question_text }}<br>
{% if question.question_category == "MC" %}
<input type="radio" name="{{ question.id }}" value="1"> 1<br>
<input type="radio" name="{{ question.id }}" value="2"> 2<br>
<input type="radio" name="{{ question.id }}" value="3"> 3<br>
<input type="radio" name="{{ question.id }}" value="4"> 4<br>
<input type="radio" name="{{ question.id }}" value="5"> 5<br>
{% elif question.question_category == "CN" %}
<input type="checkbox" name="{{ question.id }}" value="a">a<br>
<input type="checkbox" name="{{ question.id }}" value="b">b<br>
{% elif question.question_category == "TX" %}
<textarea rows="4" cols="50" name="{{ question.id }}">Test</textarea><br>
{% endif %}
{% endfor %}
<input type="submit" value="Send" />
</form>
{% else %}
<span>No questions</span>
{% endif %}
{% endblock %}

Answer Source

This is the solution I ended up with. Edited it a bit to be more generic.

In the view there is checking if the form was loaded or submitted. If it was submitted, then check the validity of all of the forms. As there are multiple forms they are then made as formsets.

View

def answerpage(request, questionnaire_pk):
    AnswerFormSet = formset_factory(AnswerForm, extra=0)
    questions = Question.objects.filter(questionnaire=questionnaire_pk)
    qname = Questionnaire.objects.get(id=questionnaire_pk)

    if request.method == 'POST':
        answer_formset = AnswerFormSet(request.POST)
        if answer_formset.is_valid():
            for answer_form in answer_formset:
                if answer_form.is_valid():
                    instance = answer_form.save(commit=False)
                    instance.fieldValue = answer_form.cleaned_data.get('fieldValue')
                    instance.save()
            return redirect('main:calcs')
        else:
            return redirect('main:home')
    else:
        quest_id = request.session.get('questionnaire_key', defaultValue)
        question_data = [{'question': question,
                          'questionnaire_key': quest_id} for question in questions]
        answer_formset = AnswerFormSet(initial=question_data)

    combined = zip(questions, answer_formset)
    context = {
        'combined': combined,
        'answer_formset': answer_formset,
        'qname': qname,
    }
    return render(request, 'main/questionnaire.html', context)

The form is a modelform with the default widgets overwritten.

Form

class AnswerForm(forms.ModelForm):
    class Meta:
        model = QuestionAnswer
        exclude = ['']
        widgets = {
            'field1': RadioSelect(choices=CHOICES, attrs={'required': 'True'}),
            'field2': HiddenInput,
            }

Webpage renders the values from the view in to a table. Management_form is needed to handle the formset values correctly.

Webpage

{% extends "main/base.html" %}
{% load static %}
{% block content %}
<link rel="stylesheet" href="{% static 'css/survey.css' %}">
<h1>{{ qname.questionnaire_text }}</h1>
<h2>{{ qname.description }}</h2>

<form method="post">{% csrf_token %}
    {{ answer_formset.management_form }}
    <table>
        {% for question, form in combined %}
            <tr><td style="width:65%">{{ question }}{{ question.question_type }}</td><td style="width:35%">{{ form.business_id }}{{ form.question }}{{ form.questionnaire_key }}{{ form.answer_text }}</td></tr>

        {% endfor %}
    </table>
    <div class="buttonHolder">
        <input type="submit" value="Save" id="next_button"/>
    </div>
</form>

{% endblock %}
Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download