Build a simple Django Blog

Software Developer with GIS background with experience in building Web applications I am seeking to apply and expand my knowledge and skills towards working in a collaborative environment to develop quality software solutions that address and solve business problems.
Goal: Build a basic Django blog where we can create, update and delete posts using the Django admin site.
Prerequisites: Make sure that Python, Virtual Environment (venv), and pip are installed on your system.
If you want to skip the tutorial and see the full code, You can find it in my Github repository here: https://github.com/MoustafaShaaban/Simple_Django_Blog
Create directories and virtual environment:
First, let’s navigate to the directory where we want to create our project let’s Desktop. Start by creating a directory called My_Django_Blog and cd to it:
mkdir My_Django_Blog && cd My_Django_Blog
Now let's create a Virtual Environment and activate it:
python -m venv venv
source venv/bin/activate
Now we can install Django by the following command, In this tutorial, we'll be using Django version 3.2:
pip install django==3.2
Create and Configure the project:
Now, let's create a new Django project, I'll call mywebsite (you can choose any name you'd like)
django-admin startproject mywebsite .
Notice the dot ( . ) at the end of the command, It's used to create our project in the same directory where we are, Without it the project will be created in a new directory.
Next, we'll create a Django app called blog. In terminal type the following command:
python manage.py startapp blog
After this, we need to tell Django to use our new app, This is done by adding it to a file called settings.py in our project's directory:
# mywebsite/settings.py:
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# My Apps:
'blog.apps.BlogConfig',
]
Since we are in settings.py file let's configure two other settings:
- Scroll down to TEMPLATES section and make the following change:
# mywebsite/settings.py:
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates'], # Edit this line
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
This will tell Django to look for templates in a new directory we'll create later.
- Scroll down to the end of the file and add the following line to configure where your static files (like css, javascript, ..) will be:
# mywebsite/settings.py:
STATIC_URL = '/static/'
STATICFILES_DIRS = [BASE_DIR, 'static/']
Django Models:
A Model in Django is just a Python class that maps the website's database. We can add attributes and methods to our Models:
Attributes represent database fields.
And methods extend the functionality of our models.
We define our models in a file called models.py which is inside our blog app directory, open it and add the following code:
from django.db import models
from django.contrib.auth.models import User # Don't forget to add this line
from django.urls import reverse # And this one too!
class Post(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=255)
slug = models.SlugField(unique=True, null=False)
content = models.TextField()
posted_on = models.DateTimeField(auto_now_add=True)
class Meta:
""" Show new posts first """
ordering = ['-posted_on']
def __str__(self):
""" Return human readable representation of the model """
return self.title
def get_absolute_url(self):
""" A method to tell Django how to calculate the canonical URL (official url of a page) for an object """
return reverse('post-detail', kwargs={'slug': self.slug})
Here, we started by importing the User auth model which we as the author of our blog posts.
Then, we created our database model which has the following attributes:
- author: a database field with a many-to-one relationship between the author and blog posts (one author can write multiple posts), the on_delete argument means, if the author is deleted, also delete all the posts related to it.
- title: A character field represents the title of each post.
- slug: This field is used in the URL mapping.
- content: a Text field that accepts large text
- posted_on: a DateTime field used to store the time when the blog post was released.
After that we need to push the model to our database by running the following commands in terminal:
python manage.py makemigrations
python manage.py migrate
Register the model in Django admin site:
To use our model from Django admin site, we need to register it first. Open admin.py file which is inside our app directory and the following code:
from django.contrib import admin
from .models import Post # Don't forget to import the model.
class PostAdmin(admin.ModelAdmin):
search_fields = ['title', 'content']
prepopulated_fields = {'slug': ['title']}
admin.site.register(Post, PostAdmin)
Here, we created a PostAdmin class which has two attributes:
- search_fields: Allow us to search for a post by its title or contents
- prepopulated_fields: Used to automatically fill the slug field according to what we type inside the title field.
Now, we can create a super user to test our website, In terminal type the following command:
python manage.py createsuperuser
After choosing a username, email, and password, we can use it to access the admin site, In the terminal run the following command to run the development server:
python manage.py runserver
Open your web browser and navigate this URL: http://localhost:8000/admin/ and login, You'll see the following page:

You can click the add sign next to the Posts section to create new posts.
Creating Django Views:
So, now that our database is working we need to create views, According to Django docs, "A view is a callable which takes a request and returns a response". We can define views in Django as a Python Function (FBV) or as a Python Class (CBV), In this tutorial, I will use two built-in class-based generic views, ListView and DetailView.
We use ListView to create a page to represent a list of objects, while we use DetailView to represent details about a specific object.
We define our views in a file called views.py which is located in our app directory. so, open it and type the following:
from django.views.generic import ListView, DetailView
from .models import Post
class PostListView(ListView):
model = Post
template_name = 'post_list.html'
context_object_name = 'posts'
paginate_by = 4
class PostDetailView(DetailView):
model = Post
template_name = 'post_detail.html'
Here, we started by importing our class-based generic views from django.views.generics , and we imported our Post database model from models, the dot ( . ) here means that the models file is in the same directory as our views.py file.
Then, we created two classes here, PostListView and PostDetailView, which inherits from the ListView and DetailView generic classes.
In our PostDetailView, we first defined the model we are going to use, which is the model we created in the database, Post. then we defined the template we are going to use for listing our blog posts, which we did not create yet, we will create all our templates in the next tutorial.
Then, we defined the name we are going to use to refer to our view when we create our templates, if we did not define that name, it will take the default value that Django uses, which is 'object_list'.
Finally, we paginate the list view to display only 4 posts on each page.
In our PostDetailView view, we defined the model and the template we are going to use.
Mapping URLs:
So, now that our views are ready, we need to map our URLs.
First, open the urls.py file which is inside mywebsite directory, and add the following code:
from django.contrib import admin
from django.urls import path, include # add include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('blog.urls')), # add this
]
Here, we told Django to include a new urls file which we'll create now.
Inside the blog app directory create a new file called urls.py, and inside it add:
from django.urls import path
from .views import PostListView, PostDetailView
urlpatterns = [
# Display a list of blog posts in the homepage of our website
path('', PostListView.as_view(), name='post-list'),
# Display post details in specific url of a post
path('posts/<slug:slug>/', PostDetailView.as_view(), name='post-detail'),
]
Here we used the main path of our website to display a list of our posts. Then, we defined a URL for each of our posts depending on the slugs.
Creating Templates:
Inside our main project's directory ( mywebsite) create two directories 'templates' and static.
Inside the templates folder create 3 files, base.html, post_list.html, and post_detail.html.
In this tutorial, I downloaded the bootstrap 5 library and added both the css and javascript files inside the static folder, You can skip this step if you want to use bootstrap CDN.

base.html is our main template which other templates will inherit from, open it and type the following:
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}My Blog{% endblock title %}</title>
<link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}"> # If you want to use the bootstrap CDN replace this line and remove the script tag from the bottom of this file.
</head>
<body>
{% comment %} Start Navbar{% endcomment %}
<div class="container">
<nav class="navbar navbar-expand-lg navbar-dark bg-primary border border-rounded">
<div class="container-fluid">
<a class="navbar-brand" href="{% url 'post-list' %}">My Blog</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
</div>
</nav>
</div>
{% comment %} End Navbar{% endcomment %}
{% block content %}{% endblock content %}
{% comment %}Start Pagination{% endcomment %}
<div class="container">
<nav aria-label="Page navigation example">
<ul class="pagination">
{% if page_obj.has_previous %}
<li class="page-item">
<a href="?page={{ page_obj.previous_page_number }}" class="page-link">« PREV</a>
</li>
{% endif %}
<li class="page-item">
<a href="{{ page_obj.page_number }}" class="page-link">{{ page_obj.number }}</a>
</li>
{% if page_obj.has_next %}
<li class="page-item">
<a href="?page={{ page_obj.next_page_number }}" class="page-link">NEXT »</a>
</li>
{% endif %}
</ul>
</nav>
</div>
{% comment %}End Pagination{% endcomment %}
{% comment %}Start Footer{% endcomment %}
<div class="container">
<footer class="blog-footer bg-primary text-center text-white">
<p>Blog template built for <a href="https://getbootstrap.com/" class="text-white">Bootstrap</a> by <a href="https://twitter.com/mdo" class="text-white">@mdo</a>.</p>
<p>
<a href="#">Back to top</a>
</p>
</footer>
</div>
{% comment %}End Footer{% endcomment %}
<script src="{% static 'js/bootstrap.bundle.min.js' %}"></script>
</body>
</html>
Here, we started by creating a simple HTML template with Django template tags inside it. First, we called the static tag {% load 'static'%}
which allows us to use static files in our template, Then, we added a bootstrap navbar. Notice the href of the tag, it is calling the 'post-list' URL that we defined inside our blog/urls.py file.
This block of code {% block content %}{% endblock content %} will be replaced by the code we'll write in other templates.
lastly, we created a pagination and footer blocks.
In post_list.html we'll start by extending the base.html template, then, we'll create a for loop to display all our posts.
{% extends 'base.html' %}
{% block content %}
{% for post in posts %}
<div class="container mt-4">
<div class="card mb-3">
<div class="card-body">
<h2 class="card-title"><a href="{% url 'post-detail' post.slug %}">{{ post.title|safe }}</a></h2>
<p class="card-text">
<small class="text-muted">
By: {{ post.author|safe }} <span>In {{ post.posted_on }}</span>
</small>
</p>
<p class="card-text">{{ post.content|slice:':200'|safe }}</p>
<a href="{% url 'post-detail' post.slug %}">Read More</a>
</div>
</div>
</div>
{% endfor %}
{% endblock content %}
post_detail.html:
{% extends 'base.html' %}
{% block title %}{{ post.title|safe }}{% endblock title %}
{% block content %}
<div class="container mt-4">
<div class="card mb-3">
<div class="card-body">
<h2 class="card-title text-center"><a href="{% url 'post-detail' post.slug %}"></a>{{ post.title|safe }}</h2>
<p class="card-text text-center">
<small class="text-muted">
By: {{ post.author|safe }} <span>In {{ post.posted_on }}</span>
</small>
</p>
<p class="card-text">{{ post.content|safe }}</p>
<a href="{% url 'post-list' %}">Go Back</a>
</div>
</div>
</div>
{% endblock content %}
Now our blog is ready go ahead and add some posts.
Conclussion:
In this tutorial, we created a very simple Django blog, I know it is not the best-looking blog because I wanted to concentrate only on the backend code, and show you how powerful Django is in building websites.
So, I hope you like this tutorial, and If you have any questions or feedback, I'll be very happy to hear it. Thanks for reading, and have a wonderful day :D
Cover credit: https://www.pexels.com/photo/blog-icon-information-internet-262508/