How To Add a Text Editor To A Django Blog With Summernote

Building a blog with Django is a popular project for beginners but how many people would actually use the end result?

Most tutorials use a TextField to store the post body with HTML's default textarea widget. This isn’t very good on its own because the post content just appears as a block of unformatted text.

What we need is the ability to format our posts. We need headings, links, bold, and italic text. We might want to include images in the post body. We also need a text editor to make the writing process nicer and see what our post will look like without reloading the page.

The solution is to install a 3rd party package that will provide us with a text editor and render formatted posts. The package should allow us to add images to the post body.

Summernote

I spent an evening looking at different markdown plugins and text editors. My personal favourite is called Summernote (docs). Here’s why:

  • You won’t need to change your model. It works by storing HTML in a TextField. This means you won’t have to write any custom migrations to get it working with your existing posts.

  • It supports images

  • It provides a WYSIWYG (What You See Is What You Get) editor. It doesn’t support markdown but you will be able to see what your post will look like.

  • It is easy to install

Steps

This tutorial will go through how to add Django Summernote to a basic blog. These are the steps we need to take:

  1. Install django-summernote

  2. Update our project settings

  3. Update our project URLs

  4. Add Summernote to the blog post form

  5. Update our post detail template

  6. Add Summernote to the admin area

Code

As usual, the source code for this tutorial is available on GitHub. Download the before code to follow along or view the finished code to jump straight to the results.

Want markdown instead?

Summernote has a lot of features but it doesn’t support markdown.

William Vincent has a tutorial on adding markdown (link). It uses a package called markdown to display content. The approach is very light-weight and easy to implement but doesn’t provide an editor.

Alternatively, django-markdownx includes a Live Preview and Drag & Drop image uploads.

1. Install django-summernote

pip install django-summernote

2. Update our project settings

Django Summernote is an app, so we need to add it to our list of INSTALLED_APPS in settings.py.

# projectconfig/settings.py

INSTALLED_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
    "blog",
    "django_summernote",
]

Image uploads

If your blog post already allows users to upload images (tutorial), then you can skip to Step 3.

Summernote allows you to upload images into the post body but you need to set up your project so that Summernote has somewhere to upload the images to.

Go to settings.py and add the following:

# projectconfig/settings.py

MEDIA_URL = "/media/"

MEDIA_ROOT = os.path.join(BASE_DIR, "media")

Then, in urls.py, add the following beneath the list of url_patterns.

if settings.DEBUG:
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

What this is all going to do is make sure images uploaded via the Summernote editor are saved inside the directory specified in MEDIA_ROOT. This is just a folder called media in the base directory.

The snippet includes settings.DEBUG because this method of storing uploaded images is only suitable while your application is in development. Remember, your database doesn’t store images and a live web server is not a good place to store large volumes of uploaded user images. When deploying your application, consider an AWS S3 bucket (tutorial by testdriven.io) or Cloudinary to store your images.

Migrate

Even though we don’t need to update any of our own models, Summernote needs to make changes to the database.

The migration will create a table called Attachment. This stores the names of uploaded images, the date it was uploaded and the location of the image.

Now that Summernote has been added to our list of INSTALLED_APPS, we can make migrations to make those changes to the database.

$ python manage.py migrate

3. Update the project URLs

Go to your project urls.py. It is the one in the same folder as settings.py.

Add path('summernote/', include('django_summernote.urls')) to your URL patterns. Mine now looks like this:

urlpatterns = [
    path("posts/", include("blog.urls")),
    path("admin/", admin.site.urls),
    path("summernote/", include("django_summernote.urls")),
]

if settings.DEBUG:
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

Why does Summernote need URLs?

Summernote has two views: one to provide the editor and one to handle uploaded files.

When you add the Summernote editor to your form, you will notice a slight lag between the page loading and the editor being rendered. This is because Summernote loads the editor over AJAX. When the add post page is loaded, a GET request is made to Summernote to get the editor.

When images are added to a post and the form is submitted, it is a Summernote view which validates and saves the uploaded images.

4. Add Summernote to the blog post form

Go to blog/forms.py. We need to replace the text area widget with the Summernote rich text editor.

To do this, add this line body = forms.CharField(widget=SummernoteWidget) to your form class. My form now looks like this:

# blog/forms.py

from django import forms
from . import models

from django_summernote.widgets import SummernoteWidget


class PostForm(forms.ModelForm):
    class Meta:
        model = models.Post
        fields = ["title", "body", "category", "tags", "feature_image"]

    body = forms.CharField(widget=SummernoteWidget)

    tags = forms.ModelMultipleChoiceField(
        queryset=models.Tag.objects.all(), widget=forms.CheckboxSelectMultiple
    )

    category = forms.ModelChoiceField(
        queryset=models.Category.objects.all(), widget=forms.RadioSelect
    )

This is all we need to do to render a text editor on our Add Post page:

Screenshot of the post form with the summernote editor.

5. Update our Template

Summernote works by converting the inputted text into HTML and stores the HTML in a standard TextField. This is why we didn’t have to update our model.

Our templates need to know if it there is HTML it needs to render.

Without doing anything to our template, our post looks like this:

Screenshot of a post showing unrendered HTML

Without the |safe tag, raw HTML is displayed in our posts.

We can fix this by adding the safe filter to the template tag.

Go into your post detail template and change <p>{{post.body}}</p> to this:

<p>{{post.body | safe}}</p>

This will render our HTML.

Use the |safe tag to render the HTML

6. Add the Summernote editor to the admin area

By default, the admin area displays post content as raw HTML like the image below:

Screenshot of the admin area on the page for a blog post. Raw HTML is displayed in the body field.

Without customising the admin for posts, raw HTML is displayed

We can add the same WYSIWYG editor to the admin area and view/edit formatted posts.

To do this, we need to import SummernoteModelAdmin and use it as a base class for our PostAdmin class.

This is what my blog/admin.py now looks like:

A screenshot of the Django admin area for a single blog post. It shows the summernote editor in the body field.

Conclusion

Django Summernote is a 3rd party plugin that adds a WYSIWYG editor. It allows you to add a full-featured text editor without requiring you to modify your own models.

Summernote works by providing a widget for your forms and stores the content as raw HTML inside a Text Field. HTML is rendered in your post detail template by adding the |safe tag.