Django By Example

4.6 (25 reviews total)
By Antonio Melé
  • Instant online access to over 8,000+ books and videos
  • Constantly updated with 100+ new titles each month
  • Breadth and depth in over 1,000+ technologies
  1. Building a Blog Application

About this book

Django is a powerful Python web framework designed to develop web applications quickly, from simple prototypes to large-scale projects. Django encourages clean, pragmatic design, and provides developers with a comprehensive set of tools to build scalable web applications.

This book will walk you through the creation of four professional Django projects, teaching you how to solve common problems and implement best practices.

The book begins by showing you how to build a blog application, before moving on to developing a social image bookmarking website, an online shop and an e-learning platform. You will learn how to build a search engine and implement a user activity stream. Furthermore, you will create a recommendation engine, an e-commerce coupon system and a content management system. The book will also teach you how to enhance your applications with AJAX, create RESTful APIs and setup a production environment for your Django projects.

After reading this book, you will have a good understanding of how Django works and how to integrate it with other technologies to build practical, advanced, web applications.

Publication date:
November 2015
Publisher
Packt
Pages
474
ISBN
9781784391911

 

Chapter 1. Building a Blog Application

In this book, you will learn how to build complete Django projects, ready for production use. In case you haven't installed Django yet, you will learn how to do it in the first part of this chapter. This chapter will cover how to create a simple blog application using Django. The purpose of the chapter is to get a general idea of how the framework works, understand how the different components interact with each other, and give you the skills to easily create Django projects with basic functionality. You will be guided through the creation of a complete project without elaborating upon all the details. The different framework components will be covered in detail throughout the following chapters of this book.

This chapter will cover the following points:

  • Installing Django and creating your first project

  • Designing models and generating model migrations

  • Creating an administration site for your models

  • Working with QuerySet and managers

  • Building views, templates, and URLs

  • Adding pagination to list views

  • Using Django class-based views

 

Installing Django


If you have already installed Django, you can skip this section and jump directly to Creating your first project. Django comes as a Python package and thus can be installed in any Python environment. If you haven't installed Django yet, here is a quick guide to installing Django for local development.

Django works well with Python versions 2.7 or 3. In the examples of this book, we are going to use Python 3. If you're using Linux or Mac OS X, you probably have Python installed. If you are not sure if Python is installed in your computer, you can verify it by typing python in the terminal. If you see something like the following, then Python is installed in your computer:

Python 3.5.0 (v3.5.0:374f501f4567, Sep 12 2015, 11:00:19) 
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>

If your installed Python version is lower than 3, or Python is not installed on your computer, download Python 3.5.0 from http://www.python.org/download/ and install it.

Since you are going to use Python 3, you don't have to install a database. This Python version comes with the SQLite database built-in. SQLite is a lightweight database that you can use with Django for development. If you plan to deploy your application in a production environment, you should use an advanced database such as PostgreSQL, MySQL, or Oracle. You can get more information about how to get your database running with Django in https://docs.djangoproject.com/en/1.8/topics/install/#database-installation.

Creating an isolated Python environment

It is recommended that you use virtualenv to create isolated Python environments, so you can use different package versions for different projects, which is far more practical than installing Python packages system wide. Another advantage of using virtualenv is that you won't need any administration privileges to install Python packages. Run the following command in your shell to install virtualenv:

pip install virtualenv

After you install virtualenv, create an isolated environment with the following command:

virtualenv my_env

This will create a my_env/ directory including your Python environment. Any Python libraries you install while your virtual environment is active will go into the my_env/lib/python3.5/site-packages directory.

If your system comes with Python 2.X and you installed Python 3.X, you have to tell virtualenv to use the latter. You can locate the path where Python 3 is installed and use it to create the virtual environment with the following commands:

zenx$ *which python3*
/Library/Frameworks/Python.framework/Versions/3.5/bin/python3
zenx$ *virtualenv my_env -p
/Library/Frameworks/Python.framework/Versions/3.5/bin/python3*

Run the following command to activate your virtual environment:

source my_env/bin/activate

The shell prompt will include the name of the active virtual environment enclosed in parentheses, like this:

(my_env)laptop:~ zenx$

You can deactivate your environment anytime with the deactivate command.

You can find more information about virtualenv at https://virtualenv.pypa.io/en/latest/.

On top of virtualenv, you can use virtualenvwrapper. This tool provides wrappers that make it easier to create and manage your virtual environments. You can download it from http://virtualenvwrapper.readthedocs.org/en/latest/.

Installing Django with pip

pip is the preferred method for installing Django. Python 3.5 comes with pip pre-installed, but you can find pip installation instructions at https://pip.pypa.io/en/stable/installing/. Run the following command at the shell prompt to install Django with pip:

pip install Django==1.8.6

Django will be installed in the Python site-packages/ directory of your virtual environment.

Now check if Django has been successfully installed. Run python on a terminal and import Django to check its version:

>>> import django
>>> django.VERSION
django.VERSION(1, 8, 5, 'final', 0)

If you get this output, Django has been successfully installed in your machine.

Django can be installed in several other ways. You can find a complete installation guide at https://docs.djangoproject.com/en/1.8/topics/install/.

 

Creating your first project


Our first Django project will be a complete blog site. Django provides a command that allows you to easily create an initial project file structure. Run the following command from your shell:

django-admin startproject mysite 

This will create a Django project with the name mysite.

Let's take a look at the generated project structure:

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

These files are as follows:

  • manage.py: A command-line utility to interact with your project. It is a thin wrapper around the django-admin.py tool. You don't need to edit this file.

  • mysite/: Your project directory consist of the following files:

    • __init__.py: An empty file that tells Python to treat the mysite directory as a Python module.

    • settings.py: Settings and configuration for your project. Contains initial default settings.

    • urls.py: The place where your URL patterns live. Each URL defined here is mapped to a view.

    • wsgi.py: Configuration to run your project as a WSGI application.

The generated settings.py file includes a basic configuration to use a SQLite database and a list of Django applications that are added to your project by default. We need to create the tables in the database for the initial applications.

Open the shell and run the following commands:

cd mysite
python manage.py migrate

You will see an output that ends like this:

Rendering model states... DONE
Applying contenttypes.0001_initial... OK
Applying auth.0001_initial... OK
Applying admin.0001_initial... OK
Applying contenttypes.0002_remove_content_type_name... OK
Applying auth.0002_alter_permission_name_max_length... OK
Applying auth.0003_alter_user_email_max_length... OK
Applying auth.0004_alter_user_username_opts... OK
Applying auth.0005_alter_user_last_login_null... OK
Applying auth.0006_require_contenttypes_0002... OK
Applying sessions.0001_initial... OK

The tables for the initial applications have been created in the database. You will learn about the migrate management command in a bit.

Running the development server

Django comes with a lightweight web server to run your code quickly, without needing to spend time configuring a production server. When you run the Django development server, it keeps checking for changes in your code. It reloads automatically, freeing you from manually reloading it after code changes. However, it might not notice some actions like adding new files to your project, so you will have to restart the server manually in these cases.

Start the development server by typing the following command from your project's root folder:

python manage.py runserver

You should see something like this:

Performing system checks...

System check identified no issues (0 silenced).
November 5, 2015 - 19:10:54
Django version 1.8.6, using settings 'mysite.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

Now, open the URL http://127.0.0.1:8000/ in your browser. You should see a page telling you the project is successfully running, as shown in the following screenshot:

You can indicate Django to run the development server on a custom host and port, or tell it that you want to run your project loading a different settings file. For example, you can run the manage.py command as follows:

python manage.py runserver 127.0.0.1:8001 \
--settings=mysite.settings

This comes in handy to deal with multiple environments that require different settings. Remember, this server is only intended for development and is not suitable for production use. In order to deploy Django in a production environment, you should run it as a Web Server Gateway Interface (WSGI) application using a real web server such as Apache, Gunicorn, or uWSGI. You can find more information about how to deploy Django with different web servers at https://docs.djangoproject.com/en/1.8/howto/deployment/wsgi/.

Additional downloadable Chapter 13, Going Live covers setting up a production environment for your Django projects.

Project settings

Let's open the settings.py file and take a look at the configuration of our project. There are several settings that Django includes in this file, but these are only a part of all the Django settings available. You can see all settings and their default values in https://docs.djangoproject.com/en/1.8/ref/settings/.

The following settings are worth looking at:

  • DEBUG is a boolean that turns on/off the debug mode of the project. If set to True, Django will display detailed error pages when an uncaught exception is thrown by your application. When you move to a production environment, remember you have to set it to False. Never deploy a site into production with DEBUG turned on because you will expose sensitive data of your project.

  • ALLOWED_HOSTS is not applied while debug mode is on or when running tests. Once you are going to move your site to production and set DEBUG to False, you will have to add your domain/host to this setting in order to allow it to serve the Django site.

  • INSTALLED_APPS is a setting you will have to edit in all projects. This setting tells Django which applications are active for this site. By default, Django includes the following applications:

    • django.contrib.admin: This is an administration site.

    • django.contrib.auth: This is an authentication framework.

    • django.contrib.contenttypes: This is a framework for content types.

    • django.contrib.sessions: This is a session framework.

    • django.contrib.messages: This is a messaging framework.

    • django.contrib.staticfiles: This is a framework for managing static files.

  • MIDDLEWARE_CLASSES is a tuple containing middlewares to be executed.

  • ROOT_URLCONF indicates the Python module where the root URL patterns of your application are defined.

  • DATABASES is a dictionary containing the settings for all the databases to be used in the project. There must always be a default database. The default configuration uses a SQLite3 database.

  • LANGUAGE_CODE Defines the default language code for this Django site.

Don't worry if you don't understand much about what you are seeing. You will get more familiar with Django settings in the following chapters.

Projects and applications

Throughout this book, you will read the terms project and application over and over. In Django, a project is considered a Django installation with some settings; and an application is a group of models, views, templates, and URLs. Applications interact with the framework to provide some specific functionalities and may be reused in various projects. You can think of the project as your website, which contains several applications like blog, wiki, or forum, which can be used in other projects.

Creating an application

Now let's create your first Django application. We will create a blog application from scratch. From your project's root directory, run the following command:

python manage.py startapp blog

This will create the basic structure of the application, which looks like this:

blog/
    __init__.py
    admin.py
    migrations/
        __init__.py
    models.py
    tests.py
    views.py

These files are as follows:

  • admin.py: This is where you register models to include them into the Django administration site. Using the Django admin site is optional.

  • migrations: This directory will contain database migrations of your application. Migrations allow Django to track your model changes and synchronize the database accordingly.

  • models.py: Data models of your application. All Django applications need to have a models.py file, but this file can be left empty.

  • tests.py: This is where you can add tests for your application.

  • views.py: The logic of your application goes here. Each view receives an HTTP request, processes it, and returns a response.

 

Designing the blog data schema


We will start by defining the initial data models for our blog. A model is a Python class that subclasses django.db.models.Model, in which each attribute represents a database field. Django will create a table for each model defined in the models.py file. When you create a model, Django offers you a practical API to query the database easily.

First, we will define a Post model. Add the following lines to the models.py file of the blog application:

from django.db import modelsfrom django.utils import timezone
from django.contrib.auth.models import User

class Post(models.Model):
    STATUS_CHOICES = (
        ('draft', 'Draft'),
        ('published', 'Published'),
    )
    title = models.CharField(max_length=250)
    slug = models.SlugField(max_length=250, 
                            unique_for_date='publish')
    author = models.ForeignKey(User,
                               related_name='blog_posts')
    body = models.TextField()
    publish = models.DateTimeField(default=timezone.now)
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)
    status = models.CharField(max_length=10, 
                              choices=STATUS_CHOICES,
                              default='draft')

    class Meta:
        ordering = ('-publish',)

    def __str__(self):
        return self.title

This is our basic model for blog posts. Let's take a look at the fields we just defined for this model:

  • title: This is the field for the post title. This field is CharField, which translates into a VARCHAR column in the SQL database.

  • slug: This is a field intended to be used in URLs. A slug is a short label containing only letters, numbers, underscores, or hyphens. We will use the slug field to build beautiful, SEO-friendly URLs for our blog posts. We have added the unique_for_date parameter to this field so we can build URLs for posts using the date and slug of the post. Django will prevent from multiple posts having the same slug for the same date.

  • author: This field is ForeignKey. This field defines a many-to-one relationship. We are telling Django that each post is written by a user and a user can write several posts. For this field, Django will create a foreign key in the database using the primary key of the related model. In this case, we are relying on the User model of the Django authentication system. We specify the name of the reverse relationship, from User to Post, with the related_name attribute. We are going to learn more about this later.

  • body: This is the body of the post. This field is TextField, which translates into a TEXT column in the SQL database.

  • publish: This datetime indicates when the post was published. We use Django's timezone now method as default value. This is just a timezone-aware datetime.now.

  • created: This datetime indicates when the post was created. Since we are using auto_now_add here, the date will be saved automatically when creating an object.

  • updated: This datetime indicates the last time the post has been updated. Since we are using auto_now here, the date will be updated automatically when saving an object.

  • status: This is a field to show the status of a post. We use a choices parameter, so the value of this field can only be set to one of the given choices.

As you can see, Django comes with different types of fields that you can use to define your models. You can find all field types in https://docs.djangoproject.com/en/1.8/ref/models/fields/.

The class Meta inside the model contains metadata. We are telling Django to sort results by the publish field in descending order by default when we query the database. We specify descending order by using the negative prefix.

The __str__() method is the default human-readable representation of the object. Django will use it in many places such as the administration site.

Note

If you come from Python 2.X, note that in Python 3 all strings are natively considered unicode, therefore we only use the __str__() method. The __unicode__() method is obsolete.

Since we are going to deal with datetimes, we will install the pytz module. This module provides timezone definitions for Python and is required by SQLite to work with datetimes. Open the shell and install pytz with the following command:

pip install pytz

Django comes with support for timezone-aware datetimes. You can activate/deactivate time zone support with the USE_TZ setting in the settings.py file of your project. This setting is set to True when you create a new project using the startproject management command.

Activating your application

In order for Django to keep track of our application and be able to create database tables for its models, we have to activate it. To do this, edit the settings.py file and add blog to the INSTALLED_APPS setting. It should look like this:

INSTALLED_APPS = (
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'blog',
)

Now Django knows that our application is active for this project and will be able to introspect its models.

Creating and applying migrations

Let's create a data table for our model in the database. Django comes with a migration system to track the changes you do to your models and propagate them into the database. The migrate command applies migrations for all applications listed in INSTALLED_APPS; it synchronizes the database with the current models and migrations.

First, we need to create a migration for the new model we just created. From the root directory of your project, enter this command:

python manage.py makemigrations blog

You should get the following output:

Migrations for 'blog':
  0001_initial.py:
    - Create model Post

Django just created a file 0001_initial.py inside the migrations directory of the blog application. You can open that file to see how a migration looks like.

Let's take a look at the SQL code that Django will execute in the database to create the table for our model. The sqlmigrate command takes migration names and returns their SQL without running it. Run the following command to inspect its output:

python manage.py sqlmigrate blog 0001

The output should look as follows:

BEGIN;
CREATE TABLE "blog_post" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "title" varchar(250) NOT NULL, "slug" varchar(250) NOT NULL, "body" text NOT NULL, "publish" datetime NOT NULL, "created" datetime NOT NULL, "updated" datetime NOT NULL, "status" varchar(10) NOT NULL, "author_id" integer NOT NULL REFERENCES "auth_user" ("id"));
CREATE INDEX "blog_post_2dbcba41" ON "blog_post" ("slug");
CREATE INDEX "blog_post_4f331e2f" ON "blog_post" ("author_id");
COMMIT;

The exact output depends on the database you are using. The output above is generated for SQLite. As you can see, Django generates the table names by combining the app name and the lowercase name of the model (blog_post), but you can also specify them in the Meta class of the models using the db_table attribute. Django creates a primary key automatically for each model but you can also override this specifying primary_key=True on one of your model fields.

Let's sync our database with the new model. Run the following command to apply existing migrations:

python manage.py migrate

You will get the following output that ends with the following line:

Applying blog.0001_initial... OK

We just applied migrations for the applications listed in INSTALLED_APPS, including our blog application. After applying migrations, the database reflects the current status of our models.

If you edit your models.py file in order to add, remove, or change fields of existing models, or if you add new models, you will have to make a new migration using the makemigrations command. The migration will allow Django to keep track of model changes. Then you will have to apply it with the migrate command to keep the database in sync with your models.

 

Creating an administration site for your models


Now that we have defined the Post model, we will create a simple administration site to manage blog posts. Django comes with a built-in administration interface that is very useful for editing content. The Django admin site is built dynamically by reading your model metadata and providing a production-ready interface for editing content. You can use it out-of-the-box, configuring how you want your models to be displayed in it.

Remember that django.contrib.admin is already included in the INSTALLED_APPS setting of our project and that's why we don't have to add it.

Creating a superuser

First, we need to create a user to manage the admin site. Run the following command:

python manage.py createsuperuser

You will see the following output. Enter your desired username, e-mail, and password:

Username (leave blank to use 'admin'): admin
Email address: [email protected]
Password: ********
Password (again): ********
Superuser created successfully.

The Django administration site

Now, start the development server with the command python manage.py runserver and open http://127.0.0.1:8000/admin/ in your browser. You should see the administration login page, as shown in the following screenshot:

Log in using the credentials of the user you created in the previous step. You will see the admin site index page, as shown in the following screenshot:

The Group and User models you see here are part of the Django authentication framework located in django.contrib.auth. If you click on Users, you will see the user you created before. The Post model of your blog application has a relationship with this User model. Remember, it is a relationship defined by the author field.

Adding your models to the administration site

Let's add your blog models to the administration site. Edit the admin.py file of your blog application and make it look like this:

from django.contrib import admin
from .models import Post

admin.site.register(Post)

Now, reload the admin site in your browser. You should see your Post model in the admin site as follows:

That was easy, right? When you register a model in the Django admin site, you get a user-friendly interface generated by introspecting your models that allows you to list, edit, create, and delete objects in a simple way.

Click on the Add link on the right of Posts to add a new post. You will see the create form that Django has generated dynamically for your model, as shown in the following screenshot:

Django uses different form widgets for each type of field. Even complex fields such as DateTimeField are displayed with an easy interface like a JavaScript date picker.

Fill in the form and click on the Save button. You should be redirected to the post list page with a successful message and the post you just created, as shown in the following screenshot:

Customizing the way models are displayed

Now we are going to see how to customize the admin site. Edit the admin.py file of your blog application and change it into this:

from django.contrib import admin
from .models import Post

class PostAdmin(admin.ModelAdmin):
    list_display = ('title', 'slug', 'author', 'publish', 
                    'status')
admin.site.register(Post, PostAdmin)

We are telling the Django admin site that our model is registered into the admin site using a custom class that inherits from ModelAdmin. In this class, we can include information about how to display the model in the admin site and how to interact with it. The list_display attribute allows you to set the fields of your model that you want to display in the admin object list page.

Let's customize the admin model with some more options, using the following code:

class PostAdmin(admin.ModelAdmin):
    list_display = ('title', 'slug', 'author', 'publish',  
                       'status')
    list_filter = ('status', 'created', 'publish', 'author')
    search_fields = ('title', 'body')
    prepopulated_fields = {'slug': ('title',)}
    raw_id_fields = ('author',)
    date_hierarchy = 'publish'
    ordering = ['status', 'publish']

Go back to your browser and reload the post list page. Now it will look like this:

You can see that the fields displayed on the post list page are the ones you specified in the list_display attribute. The list page now includes a right sidebar that allows you to filter the results by the fields included in the list_filter attribute. A search bar has appeared on the page. This is because we have defined a list of searchable fields using the search_fields attribute. Just below the search bar, there is a bar to navigate quickly through a date hierarchy. This has been defined by the date_hierarchy attribute. You can also see that the posts are ordered by Status and Publish columns by default. You have specified the default order using the ordering attribute.

Now click on the Add post link. You will also see some changes here. As you type the title of a new post, the slug field is filled automatically. We have told Django to prepopulate the slug field with the input of the title field using the prepopulated_fields attribute. Also, now the author field is displayed with a lookup widget that can scale much better than a dropdown select input when you have thousands of users, as shown in the following screenshot:

With a few lines of code, we have customized the way our model is displayed in the admin site. There are plenty of ways to customize and extend the Django administration site. Later in this book, we will cover this further.

 

Working with QuerySet and managers


Now that you have a fully functional administration site to manage your blog's content, it's time to learn how to retrieve information from the database and interact with it. Django comes with a powerful database-abstraction API that lets you create, retrieve, update, and delete objects easily. The Django Object-relational Mapper (ORM) is compatible with MySQL, PostgreSQL, SQLite, and Oracle. Remember that you can define the database of your project by editing the DATABASES setting in the settings.py file of your project. Django can work with multiple databases at a time and you can even program database routers that handle the data in any way you like.

Once you have created your data models, Django gives you a free API to interact with them. You can find the data model reference of the official documentation at https://docs.djangoproject.com/en/1.8/ref/models/.

Creating objects

Open the terminal and run the following command to open the Python shell:

python manage.py shell

Then type the following lines:

>>> from django.contrib.auth.models import User
>>> from blog.models import Post
>>> user = User.objects.get(username='admin')
>>> Post.objects.create(title='One more post',
                    slug='one-more-post',
                    body='Post body.',
                    author=user)
>>> post.save()

Let's analyze what this code does. First, we retrieve the user object that has the username admin:

user = User.objects.get(username='admin')

The get() method allows you to retrieve a single object from the database. Note that this method expects one result that matches the query. If no results are returned by the database, this method will raise a DoesNotExist exception, and if the database returns more than one result, it will raise a MultipleObjectsReturned exception. Both exceptions are attributes of the model class that the query is being performed on.

Then we create a Post instance with a custom title, slug, and body; and we set the user we previously retrieved as author of the post:

post = Post(title='Another post', slug='another-post', body='Post body.', author=user)

Note

This object is in memory and is not persisted to the database.

Finally, we save the Post object to the database using the save() method:

post.save()

This action performs an INSERT SQL statement behind the scenes. We have seen how to create an object in memory first and then persist it to the database, but we can also create the object into the database directly using the create() method as follows:

Post.objects.create(title='One more post', slug='one-more-post', body='Post body.', author=user)

Updating objects

Now, change the title of the post into something different and save the object again:

>>> post.title = 'New title'
>>> post.save()

This time, the save() method performs an UPDATE SQL statement.

Note

The changes you make to the object are not persisted to the database until you call the save() method.

Retrieving objects

The Django Object-relational mapping (ORM) is based on QuerySet. A QuerySet is a collection of objects from your database that can have several filters to limit the results. You already know how to retrieve a single object from the database using the get() method. As you have seen, we have accessed this method using Post.objects.get(). Each Django model has at least one manager, and the default manager is called objects. You get a QuerySet object by using your models manager. To retrieve all objects from a table, you just use the all() method on the default objects manager, like this:

>>> all_posts = Post.objects.all()

This is how we create a QuerySet that returns all objects in the database. Note that this QuerySet has not been executed yet. Django QuerySets are lazy; they are only evaluated when they are forced to do it. This behavior makes QuerySets very efficient. If we don't set the QuerySet to a variable, but instead write it directly on the Python shell, the SQL statement of the QuerySet is executed because we force it to output results:

>>> Post.objects.all()

Using the filter() method

To filter a QuerySet, you can use the filter() method of the manager. For example, we can retrieve all posts published in the year 2015 using the following QuerySet:

Post.objects.filter(publish__year=2015)

You can also filter by multiple fields. For example, we can retrieve all posts published in 2015 by the author with the username admin:

Post.objects.filter(publish__year=2015, author__username='admin')

This equals to building the same QuerySet chaining multiple filters:

Post.objects.filter(publish__year=2015)\
             filter(author__username='admin')

Note

We are building queries with field lookup methods using two underscores (publish__year), but we are also accessing fields of related models using two underscores (author__username).

Using exclude()

You can exclude certain results from your QuerySet using the exclude() method of the manager. For example, we can retrieve all posts published in 2015 whose titles don't start by Why:

Post.objects.filter(publish__year=2015)\
            .exclude(title__startswith='Why')

Using order_by()

You can order results by different fields using the order_by() method of the manager. For example, you can retrieve all objects ordered by their title:

Post.objects.order_by('title')

Ascending order is implied. You can indicate descending order with a negative sign prefix, like this:

Post.objects.order_by('-title')

Deleting objects

If you want to delete an object, you can do it from the object instance:

post = Post.objects.get(id=1)
post.delete()

Note

Note that deleting objects will also delete any dependent relationships.

When QuerySet are evaluated

You can concatenate as many filters as you like to a QuerySet and you will not hit the database until the QuerySet is evaluated. Querysets are only evaluated in the following cases:

  • The first time you iterate over them

  • When you slice them. for instance: Post.objects.all()[:3]

  • When you pickle or cache them

  • When you call repr() or len() on them

  • When you explicitly call list()on them

  • When you test it in a statement such as bool(), or , and, or if

Creating model managers

As we previously mentioned, objects is the default manager of every model, which retrieves all objects in the database. But we can also define custom managers for our models. We are going to create a custom manager to retrieve all posts with published status.

There are two ways to add managers to your models: You can add extra manager methods or modify initial manager querysets. The first one turns up something like Post.objects.my_manager()and the later like Post.my_manager.all(). Our manager will allow us to retrieve posts using Post.published.

Edit the models.py file of your blog application to add the custom manager:

class PublishedManager(models.Manager):
    def get_queryset(self):
        return super(PublishedManager,  
                     self).get_queryset()\
                          .filter(status='published')

class Post(models.Model):
    # ...
    objects = models.Manager() # The default manager.
    published = PublishedManager() # Our custom manager.

get_queryset() is the method that returns the QuerySet to be executed. We use it to include our custom filter in the final QuerySet. We have defined our custom manager and added it to the Post model; we can now use it to perform queries. For example, we can retrieve all published posts whose title starts with Who using:

Post.published.filter(title__startswith='Who') 
 

Building list and detail views


Now that you have some knowledge about how to use the ORM, you are ready to build the views of the blog application. A Django view is just a Python function that receives a web request and returns a web response. Inside the view goes all the logic to return the desired response.

First, we will create our application views, then we will define an URL pattern for each view; and finally, we will create HTML templates to render the data generated by the views. Each view will render a template passing variables to it and will return an HTTP response with the rendered output.

Creating list and detail views

Let's start by creating a view to display the list of posts. Edit the views.py file of your blog application and make it look like this:

from django.shortcuts import render, get_object_or_404
from .models import Post

def post_list(request):
    posts = Post.published.all()
    return render(request,
                  'blog/post/list.html',
                  {'posts': posts})

You just created your first Django view. The post_list view takes the request object as the only parameter. Remember that this parameter is required by all views. In this view, we are retrieving all the posts with the published status using the published manager we created previously.

Finally, we are using the render() shortcut provided by Django to render the list of posts with the given template. This function takes the request object as parameter, the template path and the variables to render the given template. It returns an HttpResponse object with the rendered text (normally HTML code). The render() shortcut takes the request context into account, so any variable set by template context processors is accessible by the given template. Template context processors are just callables that set variables into the context. You will learn how to use them in Chapter 3, Extending Your Blog Application.

Let's create a second view to display a single post. Add the following function to the views.py file:

def post_detail(request, year, month, day, post):
    post = get_object_or_404(Post, slug=post,
                                   status='published',
                                   publish__year=year,
                                   publish__month=month,
                                   publish__day=day)
    return render(request,
                  'blog/post/detail.html',
                  {'post': post})

This is the post detail view. This view takes year, month, day, and post parameters to retrieve a published post with the given slug and date. Notice that when we created the Post model, we added the unique_for_date parameter to the slug field. This way we ensure that there will be only one post with a slug for a given date, and thus, we can retrieve single posts by date and slug. In the detail view, we are using the get_object_or_404() shortcut to retrieve the desired Post. This function retrieves the object that matches with the given parameters, or launches an HTTP 404 (Not found) exception if no object is found. Finally, we use the render() shortcut to render the retrieved post using a template.

Adding URL patterns for your views

An URL pattern is composed of a Python regular expression, a view, and a name that allows you to name it project-wide. Django runs through each URL pattern and stops at the first one that matches the requested URL. Then, Django imports the view of the matching URL pattern and executes it, passing an instance of the HttpRequest class and keyword or positional arguments.

If you haven't worked with regular expressions before, you might want to take a look at https://docs.python.org/3/howto/regex.html first.

Create an urls.py file in the directory of the blog application and add the following lines:

from django.conf.urls import url
from . import views

urlpatterns = [
    # post views
    url(r'^$', views.post_list, name='post_list'),
    url(r'^(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})/'\
        r'(?P<post>[-\w]+)/$',
        views.post_detail, 
        name='post_detail'),
]

The first URL pattern doesn't take any arguments and is mapped to the post_list view. The second pattern takes the following four arguments and is mapped to the post_detail view. Let's take a look at the regular expression of the URL pattern:

  • year: Requires four digits.

  • month: Requires two digits. We will only allow months with leading zeros.

  • day: Requires two digits. We will only allow days with leading zeros.

  • post: Can be composed by words and hyphens.

    Note

    Creating an urls.py file for each app is the best way to make your applications reusable by other projects.

Now you have to include the URL patterns of your blog application into the main URL patterns of the project. Edit the urls.py file located in the mysite directory of your project and make it look like the following:

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

urlpatterns = [
    url(r'^admin/', include(admin.site.urls)),
    url(r'^blog/', include('blog.urls',
                           namespace='blog',
                           app_name='blog')),
]

This way, you are telling Django to include the URL patterns defined in the blog urls.py under the blog/ path. You are giving them a namespace called blog so you can refer to this group of URLs easily.

Canonical URLs for models

You can use the post_detail URL that you have defined in the previous section to build the canonical URL for Post objects. The convention in Django is to add a get_absolute_url() method to the model that returns the canonical URL of the object. For this method, we will use the reverse() method that allows you to build URLs by their name and passing optional parameters. Edit your models.py file and add the following:

from django.core.urlresolvers import reverse
Class Post(models.Model):
    # ...
    def get_absolute_url(self):
        return reverse('blog:post_detail',
                       args=[self.publish.year,
                             self.publish.strftime('%m'),
                             self.publish.strftime('%d'),
                             self.slug])

Note that we are using the strftime() function to build the URL using month and day with leading zeros. We will use the get_absolute_url() method in our templates.

 

Creating templates for your views


We have created views and URL patterns for our application. Now it's time to add templates to display posts in a user-friendly way.

Create the following directories and files inside your blog application directory:

templates/
    blog/
        base.html
        post/
            list.html
            detail.html

This will be the file structure for our templates. The base.html file will include the main HTML structure of the website and divide the content into a main content area and a sidebar. The list.html and detail.html files will inherit from the base.html file to render the blog post list and detail views respectively.

Django has a powerful template language that allows you to specify how data is displayed. It is based on template tags, which look like {% tag %} template variables, which look like {{ variable }} and template filters, which can be applied to variables and look like {{ variable|filter }}. You can see all built-in template tags and filters in https://docs.djangoproject.com/en/1.8/ref/templates/builtins/.

Let's edit the base.html file and add the following code:

{% load staticfiles %}
<!DOCTYPE html>
<html>
<head>
  <title>{% block title %}{% endblock %}</title>
  <link href="{% static "css/blog.css" %}" rel="stylesheet">
</head>
<body>
  <div id="content">
    {% block content %}
    {% endblock %}
  </div>
  <div id="sidebar">
    <h2>My blog</h2>
      <p>This is my blog.</p>
  </div>
</body>
</html>

{% load staticfiles %} tells Django to load the staticfiles template tags that are provided by the django.contrib.staticfiles application. After loading it, you are able to use the {% static %} template filter throughout this template. With this template filter, you can include static files such as the blog.css file that you will find in the code of this example, under the static/ directory of the blog application. Copy this directory into the same location of your project to use the existing static files.

You can see that there are two {% block %} tags. These tell Django that we want to define a block in that area. Templates that inherit from this template can fill the blocks with content. We have defined a block called title and a block called content.

Let's edit the post/list.html file and make it look like the following:

{% extends "blog/base.html" %}

{% block title %}My Blog{% endblock %}

{% block content %}
  <h1>My Blog</h1>
  {% for post in posts %}
    <h2>
      <a href="{{ post.get_absolute_url }}">
        {{ post.title }}
      </a>
    </h2>
    <p class="date">
      Published {{ post.publish }} by {{ post.author }}
    </p>
    {{ post.body|truncatewords:30|linebreaks }}
  {% endfor %}
{% endblock %}

With the {% extends %} template tag, we are telling Django to inherit from the blog/base.html template. Then we are filling the title and content blocks of the base template with content. We iterate through the posts and display their title, date, author, and body, including a link in the title to the canonical URL of the post. In the body of the post, we are applying two template filters: truncatewords truncates the value to the number of words specified, and linebreaks converts the output into HTML line breaks. You can concatenate as many template filters as you wish; each one will be applied to the output generated by the previous one.

Open the shell and execute the command python manage.py runserver to start the development server. Open http://127.0.0.1:8000/blog/ in your browser and you will see everything running. Note that you need to have some posts with status Published in order to see them here. You should see something like this:

Then, let's edit the post/detail.html file and make it look like the following:

{% extends "blog/base.html" %}

{% block title %}{{ post.title }}{% endblock %}

{% block content %}
  <h1>{{ post.title }}</h1>
  <p class="date">
    Published {{ post.publish }} by {{ post.author }}
  </p>
  {{ post.body|linebreaks }}
{% endblock %}

Now, you can go back to your browser and click on one of the post titles to see the detail view of a post. You should see something like this:

Take a look at the URL. It should look like /blog/2015/09/20/who-was-django-reinhardt/. We have created a SEO friendly URL for our blog posts.

 

Adding pagination


When you start adding content to your blog, you will soon realize you need to split the list of posts across several pages. Django has a built-in pagination class that allows you to manage paginated data easily.

Edit the views.py file of the blog application to import the Django paginator classes and modify the post_list view as follows:

from django.core.paginator import Paginator, EmptyPage,\
                                  PageNotAnInteger

def post_list(request):
    object_list = Post.published.all()
    paginator = Paginator(object_list, 3) # 3 posts in each page
    page = request.GET.get('page')
    try:
        posts = paginator.page(page)
    except PageNotAnInteger:
        # If page is not an integer deliver the first page
        posts = paginator.page(1)
    except EmptyPage:
        # If page is out of range deliver last page of results
        posts = paginator.page(paginator.num_pages)
    return render(request,
                  'blog/post/list.html',
                  {'page': page,
                   'posts': posts})

This is how pagination works:

  1. We instantiate the Paginator class with the number of objects we want to display in each page.

  2. We get the page GET parameter that indicates the current page number.

  3. We obtain the objects for the desired page calling the page() method of Paginator.

  4. If the page parameter is not an integer, we retrieve the first page of results. If this parameter is a number higher than the last page of results, we retrieve the last page.

  5. We pass the page number and retrieved objects to the template.

Now, we have to create a template to display the paginator, so that it can be included in any template that uses pagination. In the templates folder of the blog application, create a new file and name it pagination.html. Add the following HTML code to the file:

<div class="pagination">
  <span class="step-links">
    {% if page.has_previous %}
      <a href="?page={{ page.previous_page_number }}">Previous</a>
    {% endif %}
    <span class="current">
      Page {{ page.number }} of {{ page.paginator.num_pages }}.
    </span>
      {% if page.has_next %}
        <a href="?page={{ page.next_page_number }}">Next</a>
      {% endif %}
  </span>
</div>

The pagination template expects a Page object in order to render previous and next links and display the current page and total pages of results. Let's go back to the blog/post/list.html template and include the pagination.html template at the bottom of the {% content %} block, like this:

{% block content %}
  ...
  {% include "pagination.html" with page=posts %}
{% endblock %}

Since the Page object we are passing to the template is called posts, we are including the pagination template into the post list template specifying the parameters to render it correctly. This is the method you can use to reuse your pagination template in paginated views of different models.

Now, open http://127.0.0.1:8000/blog/ in your browser. You should see the pagination at the bottom of the post list and you should be able to navigate through pages:

 

Using class-based views


Since a view is a callable that takes a web request and returns a web response, you can also define your views as class methods. Django provides base view classes for this. All of them inherit from the View class, which handles HTTP method dispatching and other functionality. This is an alternate method to create your views.

We are going to change our post_list view into a class-based view to use the generic ListView offered by Django. This base view allows you to list objects of any kind.

Edit the views.py file of your blog application and add the following code:

from django.views.generic import ListView

class PostListView(ListView):
    queryset = Post.published.all()
    context_object_name = 'posts'
    paginate_by = 3
    template_name = 'blog/post/list.html'

This class-based view is analogous to the previous post_list view. Here, we are telling ListView to:

  • Use a specific queryset instead of retrieving all objects. Instead of defining a queryset attribute, we could have specified model = Post and Django would have built the generic Post.objects.all() queryset for us.

  • Use the context variable posts for the query results. The default variable is object_list if we don't specify any context_object_name.

  • Paginate the result displaying three objects per page.

  • Use a custom template to render the page. If we don't set a default template, ListView will use blog/post_list.html.

Now, open the urls.py file of your blog application, comment the previous post_list URL pattern, and add a new URL pattern using the PostListView class as follows:

urlpatterns = [
    # post views
    # url(r'^$', views.post_list, name='post_list'),
    url(r'^$', views.PostListView.as_view(), name='post_list'),
    url(r'^(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})/'\
        r'(?P<post>[-\w]+)/$',
        views.post_detail, 
        name='post_detail'),
]

In order to keep pagination working, we have to use the right page object that is passed to the template. Django's ListView passes the selected page in a variable called page_obj, so you have to edit your post_list.html template accordingly to include the paginator using the right variable, like this:

{% include "pagination.html" with page=page_obj %}

Open http://127.0.0.1:8000/blog/ in your browser and check that everything works the same way as with the previous post_list view. This is a simple example of a class-based view that uses a generic class provided by Django. You will learn more about class-based views in Chapter 10, Building an e-Learning Platform and successive chapters.

 

Summary


In this chapter, you have learned the basics of the Django web framework by creating a basic blog application. You have designed the data models and applied migrations to your project. You have created the views, templates, and URLs for your blog, including object pagination.

In the next chapter, you will learn how to enhance your blog application with a comment system, tagging functionality, and allowing your users to share posts by e-mail.

About the Author

  • Antonio Melé

    Antonio Melé is CTO of Exo Investing and Founder of Zenx IT. Antonio has been developing Django projects since 2006 for clients across several industries. He has been working as CTO and technology consultant for multiple technology-based startups and he has managed development teams building projects for large digital businesses. Antonio holds an Msc. in Computer Science by Universidad Pontificia Comillas. His father inspired his passion for computers and programming.

    Browse publications by this author

Latest Reviews

(25 reviews total)
Great book. It provides the details I needed to implement my application. Highly recommended.
Really great material. Exactly the kind of detail I was looking for more practice with Django.
It was the manual that I was looking for.
Book Title
Access this book, plus 8,000 other titles for FREE
Access now