Building the Full App

Introduction

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

Putting All the Pieces Together

  1. 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)),    
    )
  2. 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')
  3. 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 %}
  4. With everything in place now, go to the following URL to start using the application: http://localhost:8000/quiz/
© 2009-2011 by Ariel Ortiz. Except where otherwise noted, content on this site is licensed under a
Creative Commons Attribution-Noncommercial 3.0 License