The full application will make use of the Front Controller design pattern, which defines a single component that is responsible for processing all the requests. A front controller can centralize functions such as view selection, security, and templating, and applies them consistently across all pages or views.
The state of our application will be kept in an instance of a class called QuizState. This class will implement the state machine shown in the following figure:
The current quiz state will be stored as part of the user session (so that it retains its values between requests) and will be available to templates as quiz_state with the following attributes:
| Attribute | Description | Templates that can use it |
|---|---|---|
total
|
The total number of questions in this quiz. | All |
index
|
The current question number. Starts in 1. | All |
points
|
The number of questions correctly answered so far. | All |
finished
|
True if the current question is the last question of the quiz, false otherwise. | All |
stem
|
The stem object of the current question. |
show_question.html
|
options
|
A list with the shuffled option objects for the current question. The element at index 0 is always a "No Answer" option object. |
show_question.html
|
is_correct
|
True if the user selected the correct option for the current question, false otherwise. |
check_answer.html
|
correct_option
|
The correct option object for the current question. |
check_answer.html
|
chosen_option
|
The user chosen option object for the current question. |
check_answer.html
|
Define a unique URL for accessing our web application. Add the highlighted line in the urls.py file:
from django.conf.urls.defaults import *
# Uncomment the next two lines to enable the admin:
from django.contrib import admin
admin.autodiscover()
urlpatterns = patterns('',
# Example:
# (r'^sigcse/', include('sigcse.foo.urls')),
(r'^foo/', 'sigcse.examples.views.foo'),
(r'^quiz/', 'sigcse.examples.views.quiz'),
# Uncomment the admin/doc line below and add 'django.contrib.admindocs'
# to INSTALLED_APPS to enable admin documentation:
# (r'^admin/doc/', include('django.contrib.admindocs.urls')),
# Uncomment the next line to enable the admin:
(r'^admin/', include(admin.site.urls)),
)
Define the view function, in this case called quiz, and the QuizState class. Add the following code to the examples/views.py file:
from django.shortcuts import render_to_response
from sigcse.examples.models import Stem, Option
from random import shuffle
def quiz(request):
if 'quiz_state' in request.session:
qstate = request.session['quiz_state']
else:
qstate = QuizState()
return qstate.next_state(request)
class QuizState:
START = 0
SHOW_QUESTION = 1
CHECK_ANSWER = 2
FINISH = 3
def __init__(self):
self.stems = list(Stem.objects.all())
shuffle(self.stems)
self.total = len(self.stems)
self.index = 0
self.points = 0
self.current_state = QuizState.START
def create_response(self, page_name):
response = render_to_response(page_name, {'quiz_state': self})
response['Cache-Control'] = 'no-store'
return response
def read_choice(self, request):
choice = 0
if 'choice' in request.GET:
choice_string = request.GET['choice']
if choice_string.isdigit():
choice = int(choice_string)
if choice not in range(len(self.options) + 1):
choice = 0
return choice
def finished(self):
return self.index == self.total
def next_state(self, request):
if self.current_state == QuizState.START:
self.current_state = QuizState.SHOW_QUESTION
return self.do_show_question(request)
elif self.current_state == QuizState.SHOW_QUESTION:
self.current_state = QuizState.CHECK_ANSWER
return self.do_check_answer(request)
elif self.current_state == QuizState.CHECK_ANSWER:
if self.finished():
self.current_state = QuizState.FINISH
return self.do_finish(request)
else:
self.current_state = QuizState.SHOW_QUESTION
return self.do_show_question(request)
else:
assert False, 'Illegal quiz state'
def do_show_question(self, request):
self.stem = self.stems[self.index]
self.index += 1
self.options = list(self.stem.options.all())
shuffle(self.options)
self.options.insert(0,
Option(answer_key=False, text='No Answer'))
self.correct = [option.answer_key
for option in self.options].index(True)
request.session['quiz_state'] = self
return self.create_response('show_question.html')
def do_check_answer(self, request):
chosen = self.read_choice(request)
self.is_correct = self.correct == chosen
if self.is_correct:
self.points += 1
self.chosen_option = self.options[chosen]
self.correct_option = self.options[self.correct]
request.session['quiz_state'] = self
return self.create_response('check_answer.html')
def do_finish(self, request):
request.session.flush()
return self.create_response('finish.html')
Inside the templates folder, create the following three template files:
show_question.html, with this content:
{% extends "base.html" %}
{% block main %}
<h1>Question {{ quiz_state.index }}</h1>
<p>
{{ quiz_state.stem.text }}
</p>
<form method="get">
{% for option in quiz_state.options %}
{% if not forloop.first %}
<p>
<input type="radio" name="choice"
id="choice_{{ forloop.counter0 }}"
value="{{ forloop.counter0 }}" />
<label for="choice_{{ forloop.counter0 }}">
{{ option.text }}
</label>
</p>
{% endif %}
{% endfor %}
<input type="submit" value="Submit" />
</form>
{% endblock %}
check_answer.html, with this content:
{% extends "base.html" %}
{% block main %}
{% if quiz_state.is_correct %}
<h1>Correct!</h1>
<p>
<strong>{{ quiz_state.correct_option.text }}</strong>
is the correct answer.
</p>
{% else %}
<h1>Wrong!</h1>
<p>
You chose:
<strong>{{ quiz_state.chosen_option.text }}</strong>
</p>
<p>
But the correct answer was:
<strong>{{ quiz_state.correct_option.text }}</strong>
</p>
{% endif %}
<a href="/quiz/">Continue</a>
{% endblock %}
finish.html, with this content:
{% extends "base.html" %}
{% block main %}
<h1>The End</h1>
<p>
Final Score: {{ quiz_state.points }}/{{ quiz_state.total }}
</p>
<p>
<a href="/quiz/">Restart</a>
</p>
{% endblock %}
http://localhost:8000/quiz/