Django - CKEditor Tutorial

How to use CKEditor with Django forms and add syntax highlighted code snippets.

Introduction

CKEditor is a rich text WYSIWYG editor. This tutorial shows how to add a RichTextUploading field to a model that enables all basic CKEditor features plus allows users to upload images and insert code snippets.

Quickstart

mysite/settings.py

Add these lines to the project settings file:

INSTALLED_APPS = [

    # START

    'blog.apps.BlogConfig',

    'ckeditor',

    'ckeditor_uploader',

    # END

    'django.contrib.admin',

    'django.contrib.auth',

    'django.contrib.contenttypes',

    'django.contrib.sessions',

    'django.contrib.messages',

    'django.contrib.staticfiles',

]

 

# START

CKEDITOR_UPLOAD_PATH = 'uploads/'

MEDIA_URL = '/media/'

MEDIA_ROOT = 'media/'

# END

blog/models.py

Add RichTextUploadingField to a model:

from ckeditor_uploader.fields import RichTextUploadingField



class Post(models.Model):

    body = RichTextUploadingField(blank=True)

mysite/urls.py

Edit the project URL configuration file and include ckeditor URLs. Serve uploaded media files using the static function:

urlpatterns = [

    # HERE

    path('ckeditor/', include(

        'ckeditor_uploader.urls')),

] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

templates/blog/post_detail.html

Use the safe filter in templates:

 

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

templates/blog/post_form.html

Use the {{ form.media }} tag with forms:

<form method="post">

    {% csrf_token %}

    {{ form.media }}

    {{ form.as_p }}

    <input type="submit" value="Save">

</form>

Full tutorial

Setup

Run these commands to setup a project:

mkdir mysite && cd mysite

python3 -m venv venv

source venv/bin/activate

pip install django django-ckeditor

django-admin startproject mysite .

python manage.py startapp blog

mysite/settings.py

Add these lines to the project settings.py file:

INSTALLED_APPS = [

    # START

    'blog.apps.BlogConfig',

    'ckeditor',

    'ckeditor_uploader',

    # END

    'django.contrib.admin',

    'django.contrib.auth',

    'django.contrib.contenttypes',

    'django.contrib.sessions',

    'django.contrib.messages',

    'django.contrib.staticfiles',

]



CKEDITOR_UPLOAD_PATH = 'uploads/'

MEDIA_URL = '/media/'

MEDIA_ROOT = 'media/'



TEMPLATES = [

    {

        # ...

        # HERE

        'DIRS': [os.path.join(BASE_DIR, 'templates')],

        # ...

    },

]

Add ckeditor to the INSTALLED_APPS list.

Add ckeditor_uploader to the INSTALLED_APPS list if you want to upload images.

We enable the blog app by adding its configuration class to the INSTALLED_APPS list.

CKEDITOR_UPLOAD_PATH specifies the directory where the uploaded files are stored inside the MEDIA_ROOT directory (media/uploads).

The MEDIA_URL specifies the URL that we use to serve the uploaded files to the client (mysite.com/media/uploads/2020/06/11/04.jpg).

MEDIA_ROOT is the physical directory where the files are stored. In this case the media directory in the project root.

'DIRS': [os.path.join(BASE_DIR, 'templates')] makes Django look for template files in the project root templates directory.

blog/models.py

Add a class called Post to the blog app models.py file:

from django.db import models

from ckeditor_uploader.fields import RichTextUploadingField



class Post(models.Model):

    body = RichTextUploadingField(blank=True)

The RichTextUploadingField field works like the standard TextField but also adds the CKEditor rich text capabilities to it. Use RichTextField if you don't need to upload files.

Run migrations:

python manage.py makemigrations && \

python manage.py migrate

python manage.py createsuperuser

This makes the necessary changes to the database and creates a superuser.

 

mysite/urls.py

Edit the project urls.py file and add these lines to it:

from django.contrib import admin

# START

from django.urls import include, path

from django.conf.urls.static import static

from django.conf import settings

# END



urlpatterns = [

    # START

    path('blog/', include('blog.urls')),

    path('ckeditor/', include(

        'ckeditor_uploader.urls')),

    # END

    path('admin/', admin.site.urls)

    # HERE

] + static(settings.MEDIA_URL,

           document_root=settings.MEDIA_ROOT)

Blog URLs are stored in the blog app urls.py file.

Include the ckeditor_uploader URLs to add views that can upload and browse files. By default these views require the Staff status permission.

The static function allows us to serve uploaded media files during development.

blog/urls.py

Create a file called urls.py in the blog app directory:

from django.urls import path

from .views import PostDetailView



app_name = 'blog'



urlpatterns = [

    path('<int:pk>/',

         PostDetailView.as_view(),

         name='detail'),

]

This routes mysite.com/blog/<id>/ to the PostDetailView.

 

blog/views.py

Edit the blog app views.py file and add these lines to it:

from django.views.generic import DetailView



from .models import Post



class PostDetailView(DetailView):

    model = Post

    template_name = 'blog/post_detail.html'

With the PostDetailView class we display data about one post object using the post_detail.html template file.

 

templates/blog/post_detail.html

Create a directory called templates in the project root. Add a directory called blog in it. Add a file called post_detail.html inside the blog subdirectory.

 

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

The safe filter allows us to render the necessary HTML tags that Django escapes by default. Otherwise <strong>Hello</strong> actually looks like <strong>Hello</strong> on the page, not "Hello".

Add Post

Add these lines to the blog app admin.py file:

from django.contrib import admin

from .models import Post



admin.site.register(Post)

This allows us to manage Post objects in the admin.

 

Visit admin and add a blog post with an image:

 

 

Click the Upload tab.

Choose a file.

Cick the Send it to the server button.

 

The interface allows you to change some image properties. The width and height settings just sets the element inline style: style="height:225px; width:300px". Keep this in mind if you upload large images:

 

 

Visit the blog detail page in mysite.com/blog/1/ and you should see the formatted output:

 

 

Custom Forms

CKEditor also works outside the admin. Add the following path to the blog app urls.py file:

from django.urls import path

# HERE

from .views import PostCreateView, PostDetailView



app_name = 'blog'



urlpatterns = [

    path('<int:pk>/',

         PostDetailView.as_view(),

         name='detail'),

    # HERE

    path('create/',

         PostCreateView.as_view(),

         name='create'),

]

Edit the blog app views.py file and a class called PostCreateView to it:

from django.views.generic import DetailView

# START

from django.views.generic.edit import CreateView

from django.urls import reverse

# END

from .models import Post





class PostDetailView(DetailView):

    model = Post

    template_name = 'blog/post_detail.html'

    

# HERE

class PostCreateView(CreateView):

    model = Post

    fields = ['body']

    def get_success_url(self):

        return reverse('blog:detail',

                       args=[self.object.pk])

Create a file called post_form.html in the templates/blog/ directory:

<form method="post">

    {% csrf_token %}

    {{ form.media }}

    {{ form.as_p }}

    <input type="submit" value="Save">

</form>

Use the {{ form.media }} tag to include required media assets.

Visit mysite.com/blog/create/ to create an item. Make sure you are logged-in.

 

Toolbar Customization

You can customize the toolbar by configuring the CKEDITOR_CONFIGS setting. Put this in the project settings.py file:

CKEDITOR_CONFIGS = {

    'default':

        {'toolbar': 'Custom',

         'toolbar_Custom': [

            ['Bold', 'Link', 'Unlink', 'Image'],

        ],

}}

Now the toolbar has only these options:

 

 

You can define multiple configurations like this:


C

KEDITOR_CONFIGS = {

    'default':

        {'toolbar': 'Custom',

         'toolbar_Custom': [

             ['Bold', 'Link', 'Unlink', 'Image'],

         ],

         },

    'special':

        {'toolbar': 'Special', 'height': 500,

         'toolbar_Special':

             [

                 ['Bold'],

             ],

         }

}

The special editor can only make texts bold and its default height is 500 px. You can find the default Full toolbar configuration in the CKEditor package widgets.py file:

 

site-packages/ckeditor/widgets.py

Edit the blog app models.py file and specify the configuration using the config_name argument:

class Post(models.Model):

    body = RichTextUploadingField(blank=True,

                                  config_name='special')

CodeSnippet Plugin

The ckeditor-package includes bunch of plugins that you might want to use. One of them is the codesnippet plugin. This allows us to add code snippets. Enable it like this:

CKEDITOR_CONFIGS = {

    'default':

        {'toolbar': 'Custom',

         'toolbar_Custom': [

             ['Bold', 'Link', 'Unlink', 'Image'],

         ],

         },

    'special':

        {'toolbar': 'Special', 'height': 500,

         'toolbar_Special':

             [

                 ['Bold'],

                 ['CodeSnippet'], # here

             ], 'extraPlugins': 'codesnippet', # here

         }

}

Edit a post and add some code to it using the widget:

 

def make_pizza():

    add_ingredients(['Tomatoes', 'Mozzarella', 'Basil'])

    cook()

I'm using the highlight.js library for syntax highlighting. Edit the post_detail.html template file and add these lines to it:

<!DOCTYPE html>

<html lang="en">

<head>

    <meta charset="UTF-8">

    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <title>Document</title>

    <!-- highlight.js-->

    <link rel="stylesheet"

          href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/10.0.3/styles/default.min.css">

    <script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/10.0.3/highlight.min.js"></script>

    <script>hljs.initHighlightingOnLoad();</script>

    <!-- highlight.js -->

</head>

<body>

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

</body>

</html>

Refresh the blog detail page and you should see the code snippet highlighted: