Django Blog Tutorial
Updated
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.
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:
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
.
Log in with your superuser credentials and click the "Log in" button. It will redirect you to the 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.
Click on the "+ Add" link next to Posts and add information for a title, description, and content.
Then click the "Save" button to be redirected to a page listing all Posts saved in the database.
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:
- a list view to display all blog posts
- a detail view for individual posts
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/
.
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/
.
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.