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/