Tutorials
python

Web Development with Django

In this tutorial, you're going to learn about the Django Python Web framework. You will learn Django by creating a Voting Web App entirely using the Django.

Table Of Contents

 1. What is Django

 2. Why do you want to use Django?

 2.1. Secure

 2.2. Versatile

 2.3. Portable

 2.4. Maintainable

 3. Django Code Style

 3.1. URLs

 3.2. View

 3.3. Model

 3.4. Templates

 4. Sending the request to the right view (urls.py)

 5. Handling the requests (views.py)

 6. Creating data models {models.py}

 7. Querying Data {Views.py}

 8. Rendering Data {HTML Templates}

 9. Coding Your First Django App - Voting App

 9.1. Creating A Project

 9.2. Django Server

 9.3. Creating Voting App

 9.4. Write Your First View Function

 9.5. Path() Function

 9.5.1 path() argument: route

 9.5.2 path() argument: view

 9.5.3 path() argument: kwargs

 9.5.4 path() argument: name

 9.6 Creating Models

 9.7 Activating Models

 9.8 Python Shell

 9.9 Creating An Admin User

 9.10 Making Votings App Editable In Admin Panel

 9.11 Writing More Views

 9.12 Writing Some Meaningful View Function

 9.13 Templates For Other View Functions

 9.14 Removing HardCore URLs

 9.15 NameSpace Names

 9.16 Writing Simple Form

 9.17 Writing Simple Form

 9.18 Customizing Admin Panel

 9.19 Adding Related Objects

 9.20 Style Sheet (style.css)

 10. EndNote

What is Django?

Django is a high-level Python Web framework. It helps to create websites with ease. Django takes care of most of the irritating parts in Web Development. It is free and open source Web framework with an excellent documentation.

Why do you want to use Django?

Secure

Django helps developers avoid many security mistakes. Django developed the framework to do the right things by the protecting the website automatically. For example, Django provides a secure way to manage user accounts and passwords.

Django enables protection against most of the website vulnerabilities by default. It includes SQL Injection, cross-site scripting, cross-site request forgery, and clickjacking and even more.

Versatile

Django can be used to build almost any type of websites like content management, news sites, e-commerce sites, social network sites, wikis, etc.., Django works with different format files like HTML, CSS, JS, JSON, XML, etc..,

You can almost create any site using Django with less effort.

Portable

Django is written in Python. You don't have to bother about the platform while developing apps in Django. Python runs on any platform like Linux, Windows, Mac OS X.

Maintainable

Django code is written using principles that push users to create maintainable and reusable code. Django reduces the code for websites by providing some useful block of codes for different types of areas like forms, admin management system, etc.., Django follows a pattern called Model View Controller (MVC).

Django Code Style

Django Web application

URLs

A URL mapper is used to redirect the HTTP requests to appropriate view based on the request URL. It also matches the particular patterns of strings or digits that appear in the URL and passes these to the view functions as a data.

View

A View is a request handler function, which receives HTTP requests and returns the HTTP responses. Views access the data needed to satisfy requests via models and formatting the responses to templates.

Model

Models are the Python objects that define the structures of application's data. It provides to manage (Create, Update, Read, Delete) application's data into the database.

Templates

A Template is a text file defining the layout of a file. A view can dynamically create an HTML page using an HTML template, populating it with data from a model. A template can be used to define the structure of any type of file; it doesn't have to be HTML.

Till now you have seen the Definition, Features and Main Parts of Django.

Now, you are going to learn how the main parts of Django are looking like. Let's start.....

Sending the request to the right view (urls.py)

A URL mapper in the Django project is urls.py. In the below example, the mapper called as urlpatterns defines different types of url patterns and corresponding view function. If an HTTP request is received that has a URL matching in the urlpatterns invokes the corresponding view function and passes that request.

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

urlpatterns = [
    path('admin/', admin.site.urls),
    path('question<int:id>/', views.question_detail, name = 'question_detail'),
    path('questions', include('questions.urls')),
]

The urlpatterns object is a Python list with items path().

The first argument that the path() takes is the pattern(Route) that will be matched.

path() methods use angle brackets (<>) to capture the data from the URL and passes to the view function as a named argument(s). For example, if you have10 question and the url looks like this 'questions/1', then the <> takes the id, i.e., 1 from the URL to display corresponding content in the pages. You will see this in detail in coming Voting App Tutorial With Django.

The second argument is another function that invokes corresponding functions when the URL is matched. The notation views.question_detail indicates that the function is called question_detail(). This can be found in the views module (views.py).

A third argument name is an option which can be used to shorten the URL. You will learn about it later in this article.

urls are usually stored in a file called urls.py

Handling the requests (views.py)

Views are the heart of the Django applications. They are responsible for the HTTP requests and HTTP responses. In between they perform some activities like database operations, rendering HTML templates, etc.,

The example below shows a minimal view function index(). It receives the HttpRequest as a parameter request and returns the HttpResponse object. In this example we don't do anything with a request, we simply return a string.

# filename: views.py
from django.http import HttpResponse

def index(request):
    # Get a HttpRequest - request
    # perfoms operations according to the request
    # Returning a HttpResponse
    return HttpResponse("Simple Django HttpReponse")

Views are usually stored in a file called views.py

Creating data models {models.py}

The code below shows how to create a simple model in Django.

from django.db import models

class Question(models.Model):
    question_text = ChatField(max_length = 200)

Querying Data {Views.py}

Django provides a simple Data query API for the database.

The code is shown below, how to select all the questions from the Question model.

# filename: views.py

from django.shortcuts import render
from .models import Question

def index(request):
    all_questions = Question.objects.all();
    return render(request, 'templates/index.html', {'all_questions' : all_questions})

The function render() is the shortcut method to create a HttpResponse that is sent back to the browser. It creates an HTML file by combining the corresponding template and data we inserted, i.e., the variable 'all_questions'. It inserts the data that we provided in the HTML file. You can learn more in the later tutorial.

Rendering Data {HTML Templates}

Templates allow you to specify the structure of the output document. Templates are often used to create HTML documents. You can also create other types of documents also if needed like CSS, JS, XML, JSON, etc.., Django has its own template system. If you want you can also use the popular Python library called Jinja2 for template system.

The code shown below is the HTML template file. It is called by the function render().

You have to write the python code in between {% %} pounds, so that Django recognizes that it is a Python code. And to display the variable in HTML template, you have to use {{ }}. Write a variable that you have to display in between 2 curly braces. You have to close the for loop and if questions by writing end(here for or if). See the example...

It will display all the question(s) that are present in questions list in HTML template.

# filename: index.html

<!DOCTYPE html>
<html lang='en'>

<head>
    <title> Index </title>
</head>

<body>
    <div id='question'>
        {% for question in questions %}
            {{ question }}
        {% endfor %}
    </div>
</body>
</html>

Till now you have learned some keywords and methods in the Django framework. Now, let's learn by creating a simple Voting app in Django.

Let's start learning by doing...

Coding Your First Django App - Voting App

Now, You will learn how to create projects and apps in Django.

Note

  • I strictly recommend you to follow this tutorial by doing.

To develop a Voting app, you need to have basic knowledge of HTML, CSS, and Python. A little bit of JS will boost your Site.

2 Main parts to develop the Voting app:-

  • A public site that people can and interact with it.
  • An Admin Site to modify the content which includes Updation, Deletion, and Insertion.

First of all, install the Django in your development environment. It is best practice to make a development environment for Python projects. If you don't know how to create virtual environments for Python projects, check here.

To install Django Simply run the following code in your terminal or cmd.

pip install django

If you have any installation problems, refer here.

After installing Django start creating the project.

Creating A Project

Open the command line and go to your project directory, where you have to build it. Run the following command.

$ django-admin startproject first_site

This will create a first_site directory in the current directory. You have to work in the first_site.

Let's what the first_site directory contains.

first_site/
    manage.py
    first_site/
        __init__.py
        settings.py
        urls.py
        wsgi.py

What are those files for?

  • The outer first_site/ is just for the project files. Its name doesn't require to Django. You can rename it to anything you like.
  • manage.py: A command line utility to interact with the Django in various ways. You can read all the details about manage.py at django-manage.
  • The inner first_site/ directory is the actual Python package container for your project.
  • first_site/_init__.py: It's an empty file that tells Python that this directory should be considered as a Python package. If you want to read more about Python packages, go to Python Packages Official Docs.
  • first_site/settings.py: It helps to configure the Django. You can learn more about at django settings.
  • first_site/urls.py: This is the Django declaration for your project. You can read all the documentation at django urls.
  • first_site/wsgi.py: It helps while you're uploading your website to the servers. See How to Serve With WSGI for more information.

Django Server

Let's run the Django server to check whether it's working or not. To run the server go to the outer first_site directory and run the following command.

$ python manage.py runserver

You'll see the following output in the command line.

Performing system checks...

System check identified no issues (0 silenced).

You have 15 unapplied migration(s). Your project may not work correctly until you apply the migrations for an app(s): admin, auth, content types, sessions.
Run 'python manage.py migrate' to apply them.
August 10, 2018 - 19:54:23
Django version 2.1, using settings 'polls_site.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.

You started the Django server. Django server was written in Python. Let's check it by visiting the url http://127.0.0.1:8000/ or http://localhost:8000/ in your browser.

You will see the following page in your browser.

To change the port of the server just add port number at the end of the command.

$ python manage.py runserver 8111

Now, it will serve port at 8111. You don't need to restart the Django server when you make changes in your project. It will automatically load all the changes you make in the project.

Creating The Voting App

Now, your project is ready to develop apps. All the websites contain some apps like user login system, news feed, etc.., based on the type of website.

You can simply create apps in Django project. Django automatically generates the basic structure for your apps.

To create your app, make sure that you are in the same directory as manage.py, i.e., change the directory to outer first_site. Type the following command to create an app inside your project.

$ python manage.py startapp votings

That'll create an app called votings in the outer first_site directory. The structure of the file is...

votings/
    __init__.py
    admin.py
    apps.py
    migrations/
        __init__.py
    models.py
    tests.py
    views.py

This directory contains all the files related to votings app.

Write Your First View Function

Let's write the first view function. Open the file votings/views.py and write the following code.

# filename: first_site/votings/views.py

from django.http import HttpResponse

def index(request):
    return HttpResponse("Wow! Writing First Web App In Django!")

This is the most straightforward view you have in Django. To call the view functions, you need to map it to the URL.

For this, you need to create a URLconf file called urls.py in the votings directory. After creating the urls.py file your votings directory will look like...

votings/
    __init__.py
    admin.py
    apps.py
    migrations/
        __init__.py
    models.py
    tests.py
    urls.py
    views.py

In the votings/urls.py file write the following code. Don't copy:

# filename: first_site/votings/urls.py

from django.urls import path
from . import views

urlpatterns = [
    path('', views.index, name='index'),
]

Now, you have to include the votings.urls in the root URLconf, i.e., first_site/urls.py.

In the first_site/urls.py, add an import for django.urls.include and insert an include() with the string argument votings.urls int the urlpatterns list. Your file will look like this...

# filename: first_site/urls.py

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

urlpatterns = [
    path('votings/', include('votings.urls')),
    path('admin/', admin.site.urls),
]

The include() function allows referencing other URLconfs. Whenever Django encounters an include(), it sends a string that matches to the included URLconf("votings.urls") for the further processing.

You can also change the " votings" url to anything you like "voting_app", " votings_site", etc..,

You should always use include() the when you want to add other URL patterns. admin.site.urls is the only exception to it.

You have now all connected the index to the URLconf. Let's verify whether it's working are not, run the following command:

Note: You always be sure that the directory is outer first_site while running the commands.

Run the Django server with the following command.

$ python manage.py runserver

Go to http://localhost:8000/votings/ in your browser. You will see the text "Wow! Writing First Web App In Django!", which you defined in the index view.

If you are getting an error check that you are going to https://localhost:8000/votings/ and not http://localhost:8000/

Path() Function

You have to know about the path() function. It will help you in future in this tutorial.

The path() function takes 4 arguments in which, 2 required and 2 optional. Required arguments are route and view. Optional arguments are kwargs and name. Let's see all arguments in brief...

path() argument: route

route is a string that contains a URL pattern. While processing a request, Django starts at urlpatterns and goes a ways down the list until it matches a pattern.

For example, in a request to "http://localhost:8000/votings/" the URLconf will look for "votings/".

path() argument: view

When Django finds a matching pattern, it calls a specified view function with HttpRequest as the first argument and captured data as the keyword arguments, i.e., "kwargs".

path() argument: kwargs

Keywords can be passed through the dictionary to the target view.

path() argument: name

Naming your URL helps you to minimize the link url. Especially when you're working with the templates, it helps a lot. You will see this later while developing the app.

Learn more about path if you want.

Creating Models

Now we'll define models - models used to layout the database, with the additional metadata.

In this simple app, we'll create 2 models Question and Choice. A Question has a question and a publication date. An Choice has a text field and vote count. Each Choice is related to a Question.

These can be implemented in Python using classes. Write the following code in votings/models.py file.

# filename: first_site/votings/models.py

from django.db import models

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

class Choice(models.Model):
    question = models.ForeignKey(Question, no_delete = models.CASCADE)
    choice_text = models.CharField(max_length = 200)
    votes = models.IntegerField(default = 0)

Each model is represented by a class that subclasses django.db.models.Model. Each Model has a number of variables which represents a database field in a model.

Each field is a represented as an instance of Field class ex:- CharField for the characters, IntegerField for integers input, DateTimeField for date times, etc., This tells Django to keep the corresponding field.

The name of each Field instance, e.g., choice_text, pub_date, etc.., You will use this value in your Python code, and your database will use this as a column.

Some Feild classes have required arguments. For, example CharField requires a max_length argument.

A Field can also have various optional arguments, in this case, we have set default value 0.

ForeignKey used to define a relationship. This tells Django that each Choice is related to a single Question. Django supports all database relations like many-to-many, many-to-one, one-to-one.

Activating Models

A small bit of code gives extensive information to Django. With Models, Django can be able:

  • To create a database schema (CREATE TABLE questions) for your app
  • To create a Python database-access API for accessing Question and Choice objects

You need to tell the Django that you have created a new app for your project.

To include your app, you need to add a reference to its configuration class INSTALLED_APPS setting.

The VotingsConfig class is in the votings/apps.py file, so the path of that file is "votings.apps.VotingsConfig". Now, add that path in the first_site/settings.py file in the INSTALLED_APPS section. The file will look like this after you added the path.

# filename: first_site/settings.py

INSTALLED_APPS = [
    'votings.apps.VotingsConfig',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

Now, Django knows that you have created an app. Let's run the following command.

$ python manage.py makemigrations votings

You should see the following text in your command line.

Migrations for 'votings':
  votings\migrations\0001_initial.py
    - Create model Choice
    - Create model Question
    - Add field question to choice

By running the makemigrations command, you're telling Django that you have made some changes to your models and you had like the changes to be stored as migrations.

You can also read your migration for your new model if you like at 'votings/migrations/0001_initial.py'. But it's not a human-readable format.

Don't worry you have a command to display your model as a human-readable code. Run the following sqlmigrate command along with the app name. It returns the SQL

$ python manage.py sqlmigrate votings 0001

You should see something similar to the following:

BEGIN;
--
-- Create model Choice
--
CREATE TABLE "votings_choice" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "choice_text" varchar(200) NOT NULL, "votes" integer NOT NULL);
--
-- Create model Question
--
CREATE TABLE "votings_question" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "choice_text" varchar(200) NOT NULL, "pub_date" datetime NOT NULL);
--
-- Add field question to choice
--
ALTER TABLE "votings_choice" RENAME TO "votings_choice__old";
CREATE TABLE "votings_choice" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "choice_text" varchar(200) NOT NULL, "votes" integer NOT NULL, "question_id" integer NOT NULL REFERENCES "votings_question"
("id") DEFERRABLE INITIALLY DEFERRED);
INSERT INTO "votings_choice" ("id", "choice_text", "votes", "question_id") SELECT "id", "choice_text", "votes", NULL FROM "votings_choice__old";
DROP TABLE "votings_choice__old";
CREATE INDEX "votings_choice_question_id_3502aac4" ON "votings_choice" ("question_id");
COMMIT;

Now, run the migrate command to create those models in your database.

$ python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, content types, votings, sessions
Running migrations:
  Rendering model states... DONE
  Applying votings.0001_initial... OK

The migrate takes all the migrations you have applied and runs them in your database.

Migrations are very powerful in Django while developing the apps. This is specialized in upgrading your database live, without losing the data.

Whenever you made changes to your models, you have to follow the 2 steps for the migration.

  • Run the python manage.py makemigrations to create migrations for those changes you made.
  • Run the python manage.py migrate to apply those changes to the database.

Python Shell

Now, let's get into the interactive Python shell. To invoke the Python shell run the following code.

$ python manage.py shell

Once you're in the shell, explore the database API

>>> from votings.models import Question, Choice # Importing the model classes
>>> Question.objects.all()
<QuerySet []>
>>> from django.utils import timezone
# creating new question
>>> s = Question(question_text="What's new?", pub_date=timezone.now())
# saving the satement
>>> s.save()
>>> s.id
1
>>> s.question_text
"What's new?"
>>> s.pub_date
datetime.datetime(2018, 8, 12, 5, 6, 24, 408441, tzinfo=<UTC>)
# changing the question text
>>> s.question_text = "Favourite Language"
>>> s.save()
>>> s.question_text
'Favourite Language'
>>> Question.objects.all()
<QuerySet [<Question: Question object (1)>]>

Wait for a minute isn't helpful for the representation of Question objects.

Let's fix that, by modifying the Question model in votings/models.py file and adding a __str__ method to both Question and Choice.

# filename: first_site/votings/models.py

from django.db import models

class Question(models.Model):
    # other methods

    def __str__(self):
        return self.choice_text

class Choice(models.Model):
        # other methods

        def __str__(self):
            return self.choice_text

Save these changes and run the python manage.py shell again.

>>> from votings.models import Question
>>> Question.objects.all()
# now, you see the choice_text
<QuerySet [<Question: Favourite Language>]>

Creating An Admin User

Django provides a unique user interface for the Admin to manage the site. First of all, you have to create a superuser to use that admin panel provided by the Django. Run the following command to create a superuser.

$ python manage.py createsuperuser

Enter your username and press enter.

Username: admin

It asks for an email address, provides if you want or just ignore by pressing enter.

Email address: admin@gmail.com

The last step is to enter the password. It will ask to enter password the twice for the confirmation.

Password: **********
Password (again): *********
Superuser created successfully.

Django admin panel is activated by default. To see the admin panel first start the Django server by running the following command.

$ python manage.py runserver

Now, open a web browser and go to 'http://localhost:8000/admin/', you will see the following login form.

Now, try to login with your superuser username and password. You will see the Django admin index page once you logged in.

You can see the Groups and Users sections. It's a default functionality provided by the Django to modify Users and create Groups if you want.

Making Votings App Editable In Admin Panel

You need to add the models you have created to the admin panel, in order to change the content. To do so, open the votings/admin.py file, and edit like this.

# filename: first_site/votings/admin.py

from django.contrib import admin

from .models import Question

admin.site.register(Question)

Now, you have registered the Question. Django will display this, on the index page of the admin panel.

Click Question there you will see all the questions that are in the database. Let's you choose one to change it.

Click 'Favourite Language' question to edit it.

In our votings app, we have the following four views.

  • Question index page - displays the latest questions
  • Question detail page - displays detail question with a vote form to vote
  • Question results page - display results for the particular question
  • Voting action - handles voting for a specific choice in a particular question

Writing More Views

Now, let's add a few more views to votings/views.py file. These views are a bit different as they take the arguments.

# filename: first_site/votings/views.py

def detail(request, question_id):
    return HttpResponse("You're looking at question %s." % question_id)

def results(request, question_id):
    response = "You're looking at the results of question %s."
    return HttpResponse(response % question_id)

def vote(request, question_id):
    return HttpResponse("You're voting on question %s." % question_id)

Write these new views into the votings/urls.py file by adding the path.

# filename: first_site/votings/urls.py

from django.urls import path

from . import views

urlpatterns = [
    # ex: /votgins/
    path('', views.index, name='index'),
    # ex: /votings/5/
    path('<int:question_id>/', views.detail, name='detail'),
    # ex: /votings/5/results/
    path('<int:question_id>/results/', views.results, name='results'),
    # ex: /votings/5/vote/
    path('<int:question_id>/vote/', views.vote, name='vote'),
]

Take a look in your browser at '/votings/7/'. It'll run the detail() method and displays whatever ID you've provided in the URL. Try '/votings/7/results/' and '/votings/7/vote/', you'll see the respective view method.

When somebody requests a page from your website – say, '/votings/7/, Django will load the first_site.urls Python module because it’s pointed to by the ROOT_URLCONF setting. It finds the variable named urlpatterns and traverses the patterns in order.

After finding the match at 'votings/', it strips off the matching text ('votings/') and sends the remaining text – '7/' – to the 'votings.urls' URLconf for further processing. There it matches '<int:question_id>/', resulting in a call to the detail() view like so:

detail(request = <HttpRequest object>, question_id = 7)

The question_id = 7 comes from the <int:question_id>. Using brackets helps to "Captures" a part of URL and sends it as a keyword to the view functions.

Writing Some Meaningful View Function

Here is the new index() view function, which displays all the questions from the database.

# filename: first_site/votings/views.py

from django.http import HttpResponse

from .models import Question


def index(request):
    question_list = Question.objects.all()
    output = '-----'.join([s.question_text for s in question_list])
    return HttpResponse(output)

# Leave the rest of the views (detail, results, vote) unchanged

It's tough to see the web pages like that. You have to edit the design of the pages. To do so, you need HTML templates.

First, create a directory called templates in your votings directory. Django will look for the templates in there.

Within the templates, directory creates another directory called votings. This helps to avoid clashes with another app templates. Now, your directory for the template is 'votings/templates/votings/index.html'. Django will simply look for the 'votings/index.html'.

Write the following code into the index template.

# filename: first_site/votings/templates/index.html

{% if question_list %}
    <ul>
    {% for question in question_list %}
        <li><a href="/votings/{{ question.id }}/">{{ question.question_text }}</a></li>
    {% endfor %}
    </ul>
{% else %}
    <p>No polls are available.</p>
{% endif %}

Now, you have to update the view function to use that template.

# filename: first_site/votigns/views.py

from django.shortcuts import render

from .models import Question


def index(request):
    question_list = Question.objects.all()
    context = {'question_list': question_list}
    return render(request, 'votings/index.html', context)

The render() function takes the request object as its first argument, a template name as its second argument and a dictionary as its optional third argument. It returns a HttpResponse object of the given template rendered with the given context.

Checking for the errors while rendering the template pages.

# filename: first_site/votings/views.py

from django.shortcuts import get_object_or_404, render

from .models import Question
# ...
def detail(request, question_id):
    question = get_object_or_404(Question, pk = question_id)
    return render(request, 'votings/detail.html', {'question': question})

Templates For Other View Functions

Back detail() view of our votings app. It will have a question variable. Write the following code into votings/detail.html.

# filename: first_site/votings/templates/detail.html

<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
    <li>{{ choice.choice_text }}</li>
{% endfor %}
</ul>

The template system uses the {{ variable name }} to display the variables.

It uses {% code %} for the loops, conditionals, etc..,

Read more about templates here.

Removing HardCore URLs

Hardcore URLs looks like this...

<li><a href="/votings/{{ question.id }}/">{{ question.question_text }}</a></li>

The problem arises with this when you have to use long URLs. You can avoid this by using the {% url %} tag.

<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>

The way this works is by looking up the URL definition as specified in the votings.urls module. You can see exactly where the URL name of detail is defined below:

path('<int:question_id>/', views.detail, name = 'detail')

NameSpace Names

This is even more convenient to write the URLs. To use this, you've to define the app_name in the file votings/urls.py. The votings/urls.py file will look like this after adding the name.

# filename: first_site/votings/urls.py

from django.urls import path

from . import views

app_name = 'votings'

urlpatterns = [
    path('', views.index, name='index'),
    path('<int:question_id>/', views.detail, name='detail'),
    path('<int:question_id>/results/', views.results, name='results'),
    path('<int:question_id>/vote/', views.vote, name='vote'),
]

Change the URLs using the namespace like...

<li><a href="{% url 'votings:detail' question.id %}">{{ question.question_text }}</a></li>

Writing Simple Form

Let's update the detail('votings/detail.html') template and create a form to allow the user to vote.

# filename: first_site/votings/templates/detail.html

<h1>{{ question.question_text }}</h1>

{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}

<form action="{% url 'votings: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="Vote">
</form>

Brief explanation

  • The above template displays a radio button for each question choice. The value of each radio button is the associated question choice’s ID. The name of each radio button is "choice". That means, when somebody selects one of the radio buttons and submits the form, it’ll send the POST data choice = ### where ### is the ID of the selected choice. This is the basic concept of HTML forms.
  • We set the form's action to {% url 'votings:vote' question.id %}. We set the method to POST. It's essential to set the method to POST to hide the sending data in the URL.
  • forloop.counter indicates how many times the for tag has through its loop
  • Since we're creating POST request, you have to take care of Cross Site Request Forgeries. But, simply Django take care of that. You just need to add {% csrf_token %} tag.

Let's modify the vote() function to work as a real vote counter.

# filename: first_site/votings/views.py

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

from .models import Choice, 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, 'votings/detail.html', {
            'question': question,
            'error_message': "You didn't select a choice.",
        })
    else:
        selected_choice.votes += 1
        selected_choice.save()
        # Always return an HttpResponseRedirect after successfully dealing
        # with POST data. This prevents data from being posted twice if a
        # user hits the Back button.
        return HttpResponseRedirect(reverse('votings:results', args=(question.id,)))

Brief explanation

  • request.POST is a dictionary-like object which allows you to access the submitted data by key name. In this case, request.POST['choice'] returns the ID of selected choice, as a string. request.POST always return a string
  • Django also provides a request.GET for accessing the GET data in the same way.
  • request.POST['choice'] would raise a KeyError if the choice wasn't provided in the POST data. The above code checks for the KeyError and redisplays the question form with an error message if the choice isn't given.
  • After incrementing the choice count, the code returns an HttpResponseRedirect rather than the normal HttpResponse. HttpResponseRedirect takes a single argument: URL to which user redirects.
  • reverse() function in the HttpResponseRedirect, the function helps to avoid the hardcore URLs.

  • Int this case, redirect URL calls the results view to display the final page.

To view the results page, you have to write the results view function. Let's write that view function...

# filename: first_site/votings/views.py

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

Now, create 'votings/results.html' template.

# filename: first_site/votings/templates/results.html

<h1>{{ question.question_text }}</h1>

<ul>
{% for chioce in question.choice_set.all %}
    <li>{{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li>
{% endfor %}
</ul>

<a href="{% url 'votings:detail' question.id %}">Vote again?</a>

Customizing App

Web apps also need CSS, JS, and Images for the beautiful look. You can call them as static files.

First, create a directory called static in your votings app directory. Django will look for static files there, similarly how it find the templates.

Within static directory create another directory called votings and within create a file called style.css. Your file directory is votings/static/votings/style.css.

Write the following code in style.css

# filename: first_site/votings/static/style.css

li a {
    color : green;
}

Next, add the following link in the top of votings/templates/votings/index.html.

# filename: first_site/votings/templates/index.html

{% load static %}

<link rel="stylesheet" type="text/css" href="{% static 'votings/style.css' %}">

The {% static %} template tag generates an absolute URL of static files.

Reload the webpage you will see the styling. If you didn't see any changes. Restart the server and check. You will get it.

If you want to insert the images. Create a directory called anything(images) in the votings/static/votings/ directory and use the URL as "{% static 'votings/images/image-name'%}"

Customizing Admin Panel

You can modify the content in the admin panel. For, that you have to replace the admin.site.register(Question) with the following code:

# filename: first_site/votings/admin.py

from django.contrib import admin
from .models import Question

class QuestionAdmin(admin.ModelAdmin):
    fields = ['pub_date', 'question_text']

admin.site.register(Question, QuestionAdmin)

The particular change makes the following output in your admin panel.

That doesn't give much information about the fields and content. You can use fieldsets to do so. Change the admin code to:

# filename: first_site/votings/admin.py

from django.contrib import admin
from .models import Question

class QuestionAdmin(admin.ModelAdmin):
    fieldsets = [
        ('Questions',               {'fields': ['question_text']}),
        ('Date Information', {'fields': ['pub_date']}),
    ]

admin.site.register(Question, QuestionAdmin)

The first element of each tuple in fieldsets is the title of the fieldset. Here’s what our form looks like now:

Register the Choice object same as Question. Add the following code in votings/admin.py.

# filename: first_site/votings/admin.py

from .models import Question, Choice
admin.site.register(Choice)

Now, choices will be available in the admin panel.

Remove the register() for Choice and modify the Question registration code.

# filename: first_site/votings/admin.py

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

class ChoiceInline(admin.StackedInline):
    model = Choice
    extra = 3


class QuestionAdmin(admin.ModelAdmin):
    fieldsets = [
        ('Questions',               {'fields': ['choice_text']}),
        ('Date information', {'fields': ['pub_date'], 'classes': ['collapse']}),
    ]
    inlines = [ChoiceInline]

admin.site.register(Question, QuestionAdmin)

This will add Choices and Questions in the same page. By default, the choices have 3 fields. You can also have a choice to add choices.

It takes a lot of screen space to display all the fields for entering related Choice objects. For that reason, Django offers a tabular way of displaying inline related objects. You just need to change the ChoiceInline to TabularInline.

# filename: first_site/votings/admin.py

class ChoiceInline(admin.TabularInline):
    # code

Style Sheet (style.css)

* {
     margin: 0;
     padding: 0;
}
body {
     font-family: sans-serif;
}
h1 {
     text-align: center;
     margin-top: 75px;
}
ul {
     margin: 50px;
     list-style-type: none;
}
li a {
     color: green;
     text-decoration: none;
     font-size: 25px;
}
form {
     margin: 50px 0 0 715px;
}
form input[type='radio']  {
     cursor: pointer;
}
form input[type='submit'] {
     border: 1px solid orangered;
     padding: 7px;
     background: none;
     font-size: 14px;
     border-radius: 3px;
     cursor: pointer;
     margin:15px 0 0 12px;
}

EndNote

Congratulations! You have successfully completed your first Django App. Hope you enjoyed this tutorial and learned the basics of Django which helps, you to build some complex web apps.

This is the first step to start with Web Development in Python. But, this is not the end. Go to the deeper sections of Django with the official documentation at Django.

To learn more about Python, check out DataCamp's Intro to Python for Data Science course. It's free!

Want to leave a comment?