Building a Simple Quiz App with Django

Welcome to a fun journey into web development! If you’ve ever wanted to create interactive web applications but felt overwhelmed, you’re in the right place. Today, we’re going to build a simple quiz application using Django, a powerful and popular web framework for Python. Don’t worry if you’re new to Django or even web development; we’ll take it step by step, explaining everything along the way. Get ready to turn your ideas into a working app!

What is Django?

Before we dive into coding, let’s understand what Django is.

  • Django is a high-level Python web framework that encourages rapid development and clean, pragmatic design. Think of it as a toolkit that provides many ready-to-use components and best practices, so you don’t have to build everything from scratch.
  • Web Framework: A web framework is a collection of libraries and tools that help you build websites and web applications more easily and efficiently. Instead of writing all the complex parts of a website (like handling databases, user authentication, or URL routing) yourself, a framework provides pre-built solutions.

Django follows the “Don’t Repeat Yourself” (DRY) principle, which means you write less code for common tasks. It’s known for being “batteries included,” meaning it comes with many features out of the box, such as an Object-Relational Mapper (ORM), an administrative interface, and a templating engine.

  • Object-Relational Mapper (ORM): This is a fancy term for a tool that lets you interact with your database using Python code instead of raw SQL queries. It makes working with databases much simpler.
  • Templating Engine: This allows you to mix dynamic data from your Python code with static HTML, making it easy to generate web pages.

Getting Started: Setting Up Your Environment

First things first, let’s prepare our workspace.

1. Install Python

If you don’t have Python installed, head over to the official Python website and download the latest stable version. Make sure to check the “Add Python to PATH” option during installation on Windows.

2. Create a Virtual Environment

It’s good practice to create a virtual environment for each Django project.

  • Virtual Environment: This is an isolated environment where you can install project-specific Python packages without interfering with other projects or your system’s global Python installation. It keeps your project dependencies tidy.

Open your terminal or command prompt and run these commands:

mkdir quiz_app
cd quiz_app
python -m venv venv
  • mkdir quiz_app: Creates a new directory (folder) for our project.
  • cd quiz_app: Changes your current location to the newly created folder.
  • python -m venv venv: Creates a virtual environment named venv inside your project folder.

Now, activate the virtual environment:

  • On Windows:
    bash
    venv\Scripts\activate
  • On macOS/Linux:
    bash
    source venv/bin/activate

    You’ll see (venv) at the beginning of your terminal prompt, indicating that the virtual environment is active.

3. Install Django

With your virtual environment active, install Django:

pip install Django
  • pip: Python’s package installer. It’s used to install libraries and frameworks like Django.

Creating Your Django Project and App

Django projects are structured into “projects” and “apps.”

  • Project: The entire website or web application. It holds global settings and configurations.
  • App: A self-contained module within a project that performs a specific function (e.g., a blog app, a user authentication app, or in our case, a quiz app). A project can have multiple apps.

1. Start a New Django Project

From within your quiz_app directory (where venv is located), run:

django-admin startproject mysite .
  • django-admin: A command-line utility provided by Django for administrative tasks.
  • startproject mysite .: Creates a Django project named mysite in the current directory (.). The . is important to avoid an extra nested directory.

You’ll now have a structure like this:

quiz_app/
├── venv/
├── mysite/
│   ├── __init__.py
│   ├── asgi.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
└── manage.py
  • manage.py: A command-line utility for interacting with your Django project (e.g., running the server, making migrations).
  • mysite/settings.py: Contains your project’s main configuration.
  • mysite/urls.py: Defines the URL routes for your entire project.

2. Create a Django App

Next, let’s create our specific quiz app:

python manage.py startapp quiz

This creates a quiz directory with its own set of files:

quiz_app/
├── venv/
├── mysite/
│   └── ...
├── quiz/
│   ├── migrations/
│   ├── __init__.py
│   ├── admin.py
│   ├── apps.py
│   ├── models.py
│   ├── tests.py
│   └── views.py
└── manage.py
  • quiz/models.py: Where we define our database structure.
  • quiz/views.py: Where we write the logic for handling requests and returning responses.
  • quiz/admin.py: Where we register our models to be managed through Django’s admin interface.

3. Register Your App

We need to tell our Django project about the new quiz app. Open mysite/settings.py and add 'quiz' to the INSTALLED_APPS list:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'quiz', # Add your app here
]

Designing Our Quiz Models (Database Structure)

Now, let’s define the data structure for our quiz. We’ll need two main components: questions and choices for each question.

Open quiz/models.py and add the following code:

from django.db import models

class Question(models.Model):
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')

    def __str__(self):
        return self.question_text

class Choice(models.Model):
    question = models.ForeignKey(Question, on_delete=models.CASCADE)
    choice_text = models.CharField(max_length=200)
    is_correct = models.BooleanField(default=False)

    def __str__(self):
        return self.choice_text
  • models.Model: All Django models inherit from this, giving them database interaction capabilities.
  • CharField: A field for storing short text (like question text or choice text). max_length is required.
  • DateTimeField: A field for storing date and time information.
  • BooleanField: A field for storing true/false values. default=False sets its initial value.
  • ForeignKey: This creates a relationship between Choice and Question. Each Choice belongs to a Question.
    • on_delete=models.CASCADE: If a Question is deleted, all its associated Choices will also be deleted.
  • __str__(self): This special method tells Python how to represent an object of this class as a string. It’s very helpful for the admin interface.

Make Migrations

After defining your models, you need to tell Django to create the corresponding tables in your database.

python manage.py makemigrations quiz
python manage.py migrate
  • makemigrations quiz: Creates migration files for your quiz app, which are blueprints for database changes.
  • migrate: Applies these blueprints (and Django’s own initial migrations) to your database, creating the actual tables.

The Django Admin Interface

Django comes with a powerful, automatically generated administrative interface. Let’s make our quiz models available there.

Open quiz/admin.py and add:

from django.contrib import admin
from .models import Question, Choice

admin.site.register(Question)
admin.site.register(Choice)

Now, create a superuser (an administrator account) to access the admin site:

python manage.py createsuperuser

Follow the prompts to create a username, email, and password.

Finally, start the development server:

python manage.py runserver

Open your web browser and go to http://127.0.0.1:8000/admin/. Log in with the superuser credentials you just created. You should now see “Questions” and “Choices” listed. Click on them to add some quiz questions and choices! Make sure to set is_correct for at least one choice per question.

Building the User-Facing Pages: Views, URLs, and Templates

Now that we have our data, let’s build the pages users will interact with.

1. Define URLs

We need to tell Django which URL patterns should trigger which functions in our views.py.

First, create a new file quiz/urls.py:

from django.urls import path
from . import views

app_name = 'quiz' # Namespace for URLs

urlpatterns = [
    path('', views.index, name='index'), # /quiz/
    path('<int:question_id>/', views.detail, name='detail'), # /quiz/5/
    path('<int:question_id>/vote/', views.vote, name='vote'), # /quiz/5/vote/
    path('<int:question_id>/results/', views.results, name='results'), # /quiz/5/results/
]
  • path('', views.index, name='index'): When a user visits /quiz/, the index function in views.py will be called. name='index' gives this URL a short name for easy referencing in templates.
  • <int:question_id>/: This is a dynamic URL part. It captures an integer value from the URL and passes it as question_id to the view function.

Next, include these app-specific URLs in the project’s main mysite/urls.py:

from django.contrib import admin
from django.urls import include, path

urlpatterns = [
    path('admin/', admin.site.urls),
    path('quiz/', include('quiz.urls')), # Include our quiz app's URLs
]
  • path('quiz/', include('quiz.urls')): This tells Django that any URL starting with quiz/ should be handled by the quiz/urls.py file.

2. Write Views (Logic)

Views are Python functions that take a web request and return a web response. They handle the logic of fetching data, processing user input, and rendering templates.

Open quiz/views.py and add the following code:

from django.shortcuts import render, get_object_or_404
from django.http import HttpResponseRedirect
from django.urls import reverse

from .models import Question, Choice

def index(request):
    latest_question_list = Question.objects.order_by('-pub_date')[:5]
    context = {'latest_question_list': latest_question_list}
    return render(request, 'quiz/index.html', context)

def detail(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    return render(request, 'quiz/detail.html', {'question': question})

def vote(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    try:
        selected_choice = question.choice_set.get(pk=request.POST['choice'])
    except (KeyError, Choice.DoesNotExist):
        # Redisplay the question voting form.
        return render(request, 'quiz/detail.html', {
            'question': question,
            'error_message': "You didn't select a choice.",
        })
    else:
        # Check if the selected choice is correct
        if selected_choice.is_correct:
            # In a real app, you might increment a score
            pass # For now, just proceed to results
        else:
            pass # Handle incorrect answer if needed

        # You might want to save user's choice to the database for tracking
        # For this simple app, we just show results.
        return HttpResponseRedirect(reverse('quiz:results', args=(question.id,)))

def results(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    return render(request, 'quiz/results.html', {'question': question})
  • render(request, 'template_name.html', context): A shortcut function that loads a template, fills it with data from the context dictionary, and returns an HttpResponse object with the rendered output.
  • get_object_or_404(Model, **kwargs): Fetches an object from the database, or raises an Http404 error if it doesn’t exist. This prevents displaying an error page to the user if a non-existent ID is entered.
  • request.POST['choice']: Accesses data submitted through an HTML form using the HTTP POST method.
  • HttpResponseRedirect(reverse('quiz:results', args=(question.id,))): Redirects the user to another URL after a successful form submission. reverse() generates the URL from its name, making our code more robust.

3. Create Templates (Presentation)

Templates are HTML files that display dynamic content. Create a templates directory inside your quiz app, and then a quiz directory inside that (so quiz/templates/quiz/). This naming convention helps Django find templates and avoids conflicts between apps.

quiz/templates/quiz/index.html (List of questions)

<!-- quiz/templates/quiz/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Simple Quiz App</title>
    <style>
        body { font-family: sans-serif; margin: 20px; background-color: #f4f4f4; }
        ul { list-style: none; padding: 0; }
        li { background-color: white; margin-bottom: 10px; padding: 15px; border-radius: 5px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
        a { text-decoration: none; color: #007bff; font-weight: bold; }
        a:hover { text-decoration: underline; }
        h1 { color: #333; }
    </style>
</head>
<body>
    <h1>Welcome to the Quiz App!</h1>
    {% if latest_question_list %}
        <ul>
        {% for question in latest_question_list %}
            <li><a href="{% url 'quiz:detail' question.id %}">{{ question.question_text }}</a></li>
        {% endfor %}
        </ul>
    {% else %}
        <p>No questions are available.</p>
    {% endif %}
</body>
</html>
  • {% if ... %}, {% for ... %}, {% else %}: Django template tags for control flow.
  • {{ variable }}: Displays the value of a variable passed from the view.
  • {% url 'quiz:detail' question.id %}: This generates the URL for the detail view, passing the question.id as an argument. It uses the quiz namespace we defined in quiz/urls.py.

quiz/templates/quiz/detail.html (Display a question and its choices)

<!-- quiz/templates/quiz/detail.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{{ question.question_text }}</title>
    <style>
        body { font-family: sans-serif; margin: 20px; background-color: #f4f4f4; }
        .container { background-color: white; padding: 30px; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1); max-width: 600px; margin: auto; }
        h1 { color: #333; margin-bottom: 20px; }
        ul { list-style: none; padding: 0; }
        li { margin-bottom: 10px; }
        input[type="radio"] { margin-right: 10px; }
        input[type="submit"] {
            background-color: #007bff;
            color: white;
            padding: 10px 20px;
            border: none;
            border-radius: 5px;
            cursor: pointer;
            font-size: 16px;
            margin-top: 20px;
        }
        input[type="submit"]:hover { background-color: #0056b3; }
        .error { color: red; font-weight: bold; margin-bottom: 15px; }
    </style>
</head>
<body>
    <div class="container">
        <h1>{{ question.question_text }}</h1>

        {% if error_message %}<p class="error"><strong>{{ error_message }}</strong></p>{% endif %}

        <form action="{% url 'quiz:vote' question.id %}" method="post">
            {% csrf_token %}
            {% for choice in question.choice_set.all %}
                <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}">
                <label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br>
            {% endfor %}
            <input type="submit" value="Submit Answer">
        </form>
        <p><a href="{% url 'quiz:index' %}">Back to Questions</a></p>
    </div>
</body>
</html>
  • {% csrf_token %}: This is crucial for security in Django forms. It protects against Cross-Site Request Forgery (CSRF) attacks. Always include it in your forms!
  • <form action="{% url 'quiz:vote' question.id %}" method="post">: The form will submit data to the vote view for the current question using the POST method.
  • question.choice_set.all: This is how you access related objects (all the choices associated with a specific question).

quiz/templates/quiz/results.html (Display results)

<!-- quiz/templates/quiz/results.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Results for {{ question.question_text }}</title>
    <style>
        body { font-family: sans-serif; margin: 20px; background-color: #f4f4f4; }
        .container { background-color: white; padding: 30px; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1); max-width: 600px; margin: auto; }
        h1 { color: #333; margin-bottom: 20px; }
        ul { list-style: none; padding: 0; }
        li { margin-bottom: 10px; font-size: 1.1em; }
        .correct { color: green; font-weight: bold; }
        .incorrect { color: red; }
        a { text-decoration: none; color: #007bff; font-weight: bold; margin-top: 20px; display: inline-block; }
        a:hover { text-decoration: underline; }
    </style>
</head>
<body>
    <div class="container">
        <h1>Results for: {{ question.question_text }}</h1>

        <ul>
        {% for choice in question.choice_set.all %}
            <li {% if choice.is_correct %}class="correct"{% else %}class="incorrect"{% endif %}>
                {{ choice.choice_text }}
                {% if choice.is_correct %} (Correct Answer) {% endif %}
            </li>
        {% endfor %}
        </ul>

        <p><a href="{% url 'quiz:detail' question.id %}">Try this question again</a></p>
        <p><a href="{% url 'quiz:index' %}">Return to quiz list</a></p>
    </div>
</body>
</html>

This template displays all choices and marks which one is correct. In a more complex app, you’d show if the user’s specific choice was correct or incorrect. For simplicity, we just display the correct choice among all options.

Running Your Quiz App

Make sure your development server is still running (python manage.py runserver). If not, start it again.

Now, open your browser and navigate to http://127.0.0.1:8000/quiz/.

You should see:
1. A list of questions you added in the admin panel.
2. Clicking a question takes you to its detail page with choices.
3. Select a choice and submit.
4. You’ll be redirected to the results page, showing the correct answer.

Congratulations! You’ve successfully built a simple quiz application using Django!

What’s Next?

This is just the beginning! Here are some ideas to expand your quiz app:

  • Scoring System: Keep track of user scores.
  • User Accounts: Allow users to register, log in, and save their quiz progress.
  • Multiple Quizzes: Create different categories or sets of quizzes.
  • Timer: Add a time limit for answering questions.
  • Feedback: Give instant feedback on whether an answer was correct or incorrect.
  • Styling: Make it look much prettier with CSS frameworks like Bootstrap.
  • Database: Learn more about different database options like PostgreSQL.

Django is a powerful framework, and there’s a lot more to explore. Keep experimenting, keep building, and have fun with web development!


Comments

Leave a Reply