Django Blog Tutorial

This tutorial is a step-by-step guide to building a simple blog application using Django. If you are brand new to Django, I recommend first starting with the Getting Started with Django Tutorial for an overview of how Django works, and then Django Hello, World to get your feet wet. Or you can jump right in: it's up to you.

Table of Contents

Setup

Let's begin by creating a new virtual environment for this project called .venv.

# Windows
$ python -m venv .venv
$ .venv\Scripts\Activate.ps1
(.venv) $

# macOS/Linux
$ python3 -m venv .venv
$ source .venv/bin/activate
(.venv) $

Then install Django and create a new Django project called django_project.

(.venv) $ python -m pip install django
(.venv) $ django-admin startproject django_project .

The startproject command creates a new folder called django_project containing several files and a manage.py file.

├── django_project
│   ├── __init__.py
│   ├── asgi.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
└── manage.py

Run the migrate command to configure the initial database.

(.venv) $ python manage.py migrate 

A new file, db.sqlite3, containing our database, is created. Django supports multiple database backends but defaults to SQLite for local development.

Finally, use runserver to start up the local web server.

(.venv) $ python manage.py runserver 

If you open your web browser to 127.0.0.1:8000 you should see the following screen:

Django welcome page

Organizationally, a Django website consists of a single project and multiple apps for discrete functionality. Some of these apps are built-in, but you can also add new ones when building new features, such as a blog. Let's create a new app for our blog posts called posts.

(.venv) $ python manage.py startapp posts

The startapp command creates a new posts folder with several files inside of it:

└── posts
    ├── __init__.py
    ├── admin.py
    ├── apps.py
    ├── migrations
    │   └── __init__.py
    ├── models.py
    ├── tests.py
    └── views.py

It's not enough to add a new app; for Django to recognize it, the app must be added to the INSTALLED_APPS configuration within our settings.py file.

# django_project/settings.py
INSTALLED_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
    "posts",  # new
]

This posts app will contain our blog's models, views, admin, URLs, and templates.

Models

Models are how Django accesses, manages, and stores data through Python objects. To keep things simple, we will focus on a single model containing a Title, Description, and Content.

Create a new 'Post' model that extends django.db.models.Model and then add three fields for title, description, and content. There are many built-in field types you can use, as well as field options for greater customization.

# posts/models.py
from django.db import models

class Post(models.Model):
    title = models.CharField(max_length=100)
    description = models.CharField(max_length=100)
    content = models.TextField()

    def __str__(self):
        return self.title

Django has a built-in migrations framework for handling changes to models over time. Every time there is a change to the database schema, you can create a new migrations file and then apply it to the database. This process makes it much easier to revisit or roll back changes at a future date.

Use the makemigrations command to generate a new migrations file.

(.venv) $ python manage.py makemigrations
Migrations for 'posts':
  posts/migrations/0001_initial.py
    - Create model Post

As the command line output makes clear, this new file is located in posts/migrations/0001_initial.py. Then, run migrate to apply the changes to our database.

(.venv) $ python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, posts, sessions
Running migrations:
  Applying posts.0001_initial... OK

Admin

One of Django's super features is its built-in admin application that provides a visual way to interact with our data. To use it, create a superuser account from the command line.

(blog) $ python manage.py createsuperuser

Start the local server again with python manage.py runserver and then head over to the admin in your web browser at http://127.0.0.1:8000/admin.

Admin Login

Log in with your superuser credentials and click the "Log in" button. It will redirect you to the admin homepage.

Admin Homepage

By default, only the Groups and Users are visible at first. If you click on Users, you'll see information about your superuser account since that's our only user. To interact with our Post model, we'll need to add it via the posts/admin.py file manually.

# posts/admin.py
from django.contrib import admin
from .models import Post

admin.site.register(Post)

This code registers the model with the admin. The Posts model is now visible if you refresh the admin homepage.

Admin Posts Page

Click on the "+ Add" link next to Posts and add information for a title, description, and content.

Admin Add Post

Then click the "Save" button to be redirected to a page listing all Posts saved in the database.

Admin Post List

If you click on the title of a post, it takes you to the change page where you can update or delete information.

We have created a database for our blog application and learned how to manipulate the data via the admin. Now it is time to display the information on our website.

Views

Views contain the logic for a Django project. There are multiple ways to use views: function-based, class-based, or generic class-based. We will focus on generic class-based views for demonstration purposes since they are designed to handle common use cases with minimal code.

In our blog application, we only need two views:

We can write these two views with the following code thanks to the "batteries" provided by generic class-based views.

# posts/views.py
from django.views.generic import ListView, DetailView
from .models import Post


class PostListView(ListView):
    model = Post
    template_name = "post_list.html"


class PostDetailView(DetailView):
    model = Post
    template_name = "post_detail.html"

At the top, we imported ListView and DetailView and our model, Post. Then, we created two new classes called PostListView and PostDetailView. The only two fields defined are the model and template name (more on templates shortly).

And that's it! We don't need to add more logic to display either one blog post or thousands of them.

URLs

Django uses urls.py files to define URL paths for web pages. If you look at our project code, there is an existing URLs file in django_project/urls.py.

# django_project/urls.py
from django.contrib import admin
from django.urls import path

urlpatterns = [
    path("admin/", admin.site.urls),
]

This code powers the Django admin we already used by importing the admin at the top (it is a built-in app) and path, which is used to define URL patterns below. Currently, the code says that when a user goes to /admin/, they should be redirected to the admin app's URLs. We will apply a similar logic for our Posts app.

# django_project/urls.py
from django.contrib import admin
from django.urls import path, include  # new

urlpatterns = [
    path("admin/", admin.site.urls),
    path("", include("posts.urls")),  # new
]

At the top, include is imported so we can add our apps, and the path is set to the empty string, ". In other words, if a user tries to load the homepage, they will be redirected to the URLs in our posts app, so let's go set the URLs there.

Create a new file called posts/urls.py. At the top, import path and our two views, PostListView and PostDetailView. Then, define two URL paths for our detail pages and our list page. Here is the complete code.

# posts/urls.py
from django.urls import path

from .views import PostListView, PostDetailView

urlpatterns = [
    path("<int:pk>/", PostDetailView.as_view(), name="post_detail"),
    path("", PostListView.as_view(), name="post_list"),
]

Whenever a model is created, Django automatically adds a primary key for each record, meaning the first blog post has a pk value of 1, the second of 2, and so on. This is how the database keeps track of records. We can use this information in the URL pattern for detail pages.

For the PostDetailView pattern, the path is set to <int:pk>, which means displaying the primary key of the blog post as an integer. In other words, the first blog post will be at /1, the second at /2, and so on. This will make more sense when you see it in action.

The logic for the list view is more straightforward: we define the URL path again as the empty string, "" so it will appear on the homepage.

Because we are using class-based views, the as_view() class method is needed, and we add a URL name for each to help with our templates in the next section.

Templates

The last piece of the puzzle is defining our templates, which are text files used to generate HTML with information from the database dynamically. Django comes with a templating engine that provides variables, tags, and filters.

Create a templates folder in your posts app. Then add two new files: post_list.html and post_detail.html.

We will start with the post_list.html. It uses the for/endfor tags to loop over all the blog post objects in our database. When using the generic class-based view ListView, all items are available in the template's context via object_list.

We display the title and description for each post object. For the title, we add a URL link to the detail page by adding the URL name "post_detail" and passing in the requisite primary key, post.pk.

<!-- posts/templates/post_list.html -->
<h1>Blog List</h1>
{% for post in object_list %}
<div>
  <h2><a href="{% url 'post_detail' post.pk %}">{{ post.title }}</a></h2>
  <p>{{ post.description }}</p>
</div>
{% endfor %}

The detail view in post_detail.html is easier to reason about. We can access all the fields on our object and display them as follows.

<!-- posts/templates/post_detail.html -->
<h1>Blog Detail</h1>
<div>
  <h2>{{ object.title }}</h2>
  <p>{{ object.description }}</p>
  <p>{{ object.content }}</p>
</div>

Make sure the webserver is running via the python manage.py runserver command and refresh our homepage at http://127.0.0.1:8000/.

Blog List

There is the list page with all available blog posts. Click on the title link, and you'll be redirected to http://127.0.0.1:8000/1/.

Blog Detail

Conclusion

The structure is now in place to add as many blog post entries as possible via the admin. Go to http://127.0.0.1:8000/admin and add new posts, then refresh your local webpage, and they will appear. Easy!

Of course, in the real world, you might like to add some additional functionality to a blog application. For example, styling with CSS and JavaScript can be added to the templates. Consider including user accounts so that different users can create different posts. And maybe you'd like to add create/read/update/delete functionality to the website itself rather than letting every user have access to the admin.

All these things are possible and come with quick built-in solutions thanks to Django's "batteries-included" approach.

If you'd like to learn more about what Django offers, check out my book, Django for Beginners.

Join My Newsletter

Subscribe to get the latest tutorials/writings by email.

    No spam. Unsubscribe at any time.