Create a blog with Django in less than 10 minutes


Creating a blog with Django is really quick if you know the right tools for the job. I will show you how to create a good-looking blog with Summernote integration and Bootstrap 4.

Summernote is a simple but rich text editor

Setting up the project

I will assume that you have a copy of Python 3 with Django 3.0 and that you know the basic structure of a Django project. The database engine will be SQlite, which Django provides out of the box.

I strongly advise that you use a virtual environment to do this, but if you don't you should be fine.

Let's create a blog project with an app to display a list of posts.

$ django-admin startproject simple_blog
$ cd simple_blog
$ ./ startapp posts
$ touch posts/
$ mkdir -p posts/templates/posts

To integrate Summernote we will use an awesome package created by the Django community. This package integrates with Django's admin site and the Forms API. On this post I will show you the former option.

$ pip install django-summernote

Let's install both the summernote app and the posts app we created.


# 3rd party

# own

# other apps

Configuring URLs


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

from django.conf import settings

urlpatterns = [
path('summernote/', include('django_summernote.urls')),

Our project now includes the paths provided by django-summernote.

I am delaying adding the posts app's URLs for now. Django requires apps to have at least one URL pattern when you include them.

Creating the post model

Apparently, the ideal length of a blog post for SEO purposes is somewhere between 1000 and 2500 words, which are many more words than I expected. Considering the average English word has 6 letters, a long and SEO-sexy post should have around 15000 characters. To be safe and to allow lengthier posts, I suggest allowing at least 25000 characters.


from django.db import models

class Post(models.Model):   
    title = models.TextField(max_length=100)
    body = models.TextField(max_length=25000)
    published_date = models.DateField(auto_now_add=True)
    updated_date = models.DateField(auto_now=True)

If you are not familiar with database migrations, you can think of them as version control for databases. A good migration system like Django's allows you to switch back and forth between database versions.

Every time we run makemigrations, Django checks if there are any model changes. If there have been changes, it attempts to create a migrations file which is also logged in the database.

We then run migrate, which attempts to apply the new changes using the new migrations file.

$ ./ makemigrations
$ ./ migrate

Even if you haven't added any models yet, running these commands should produce some changes in your database. This is because Django comes with some models that are used for things such as its user authentication system.

Creating the posts view

If you are a Django beginner, you are likely to have used function-based views so far. For this blog we will use a generic class-based view. These views have a deceivingly complicated name. They are actually easier to use than their function-based counterparts.


from django.views.generic.list import ListView

from .models import Post

class PostListView(ListView):
template_name = 'posts/posts.html'

model = Post
paginate_by = 5

By giving a ListView a model, we generate a list of objects that can be used in the template. I have added pagination as an example and it's completely optional. Had we used Django's default (dirtier) template structure, this class would only need a model to work.

A very quick introduction to Bootstrap

Bootstrap was created by some Twitter developers and is the most popular frontend framework these days.

This framework uses a grid system that splits the browser into 12 flexible columns. Bootstrap is aware of the width of your browser window, and it places it in one of five groups accordingly.

Instead of writing your own CSS, you interact with Bootstrap by using some HTML classes it provides. For example, If I want a div to be 8 columns wide when my browser screen is large, and 12 columns wide when it's small, I write

<div class="col-lg-12 col-sm-8"></div>

Other than letting you resize content, Bootstrap provides a lot of other functionality to center content, justify text, apply colour themes, etc. I will explain these classes briefly when I use them, but I suggest reading Bootstrap's documentation as well.

Creating a posts template

It's time to add a template with Bootstrap 4 and some beautiful IBM typography that you can find on Google Fonts. This template will take care of consuming a list of blog posts and displaying them.

One of Django's safety mechanisms is escaping HTML output. Since Summernote produces exactly that, we'll need to use the safe filter to correctly view the content.

On the frontend side, we will use Bootstrap to center post titles and to make the publication date stick to the right.

We will be using three variants of the same font: Sans for regular text, Mono for code and Serif for titles.


<link href="" rel="stylesheet" integrity="sha384-yrfSO0DBjS56u5M+SjWTyAHujrkiYVtRYh2dtB3yLQtUz3bodOeialO59u5lUCFF" crossorigin="anonymous">

<link href="" rel="stylesheet">
<link href="" rel="stylesheet">
<link href="" rel="stylesheet">

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


<main class="col-lg-9 col-md-12 col-sm-12 col-12">
{% for post in object_list %}
<h3 class="text-center">{{ post.title | safe }}</h3>
<h6 class="float-right">{{ post.published_date | safe }}</h6><br><br>
<div class="text-justify">{{ post.body | safe }}</div>
{% endfor %}

Wiring it all together

In order for the list view magic to work, we need to call its as_view method when we assign it a path.

This method's inner workings are a bit advanced, but let's say that when we visit this path, the as_view method will return another function that will take our request and start a request-response cycle.


from django.urls import path

from . import views

app_name = 'posts'

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


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

from django.conf import settings

urlpatterns = [
path('summernote/', include('django_summernote.urls')),
path('', include('posts.urls')),

Adding the CSS


nav {
color: #54d750;
background-color: black;

body {
font-family: 'IBM Plex Sans';

main {
background-color: white;

pre {
font-family: 'IBM Plex Mono', monospace;
border: 1px solid #80808038;
padding: 4px;

h1, h3, h4 {
font-family: 'IBM Plex Serif', sans-serif;

Using the admin site to write posts

To be able to interact with a model through the Django admin site, we need to make the site aware of that  model. This is known as registering a model in the framework's jargon.


from django.contrib import admin

from django_summernote.admin import SummernoteModelAdmin
from .models import Post

class PostAdmin(SummernoteModelAdmin):
summernote_fields = '__all__', PostAdmin)

After registering the Post model like this, Summernote automatically adds a Summernote editor to its TextField fields (title and body) on the Django admin site.

In order to log in, you will need to create a superuser. A superuser can create, edit and delete database objects.

$ ./ createsuperuser

There is a final step we need to do before we can use the Summernote editor. This editor triggers Django's clickjacking protection, but this is easily solvable by adding a line to the settings file.



Creating a post

That's all, folks! Go ahead and use this post to test your new blog, or play with the editor.

You can upload the JSON with this post like this:

$ ./ loaddata post.json