Installation

Install django-editor package:

pip install django-ckeditor 

Add ckeditor to the INSTALLED_APPS list in settings.py:

INSTALLED_APPS = [ ... 'ckeditor', ] 

Add RichTextField

Add a field to some of your models:

from ckeditor.fields import 

RichTextField description = RichTextField(blank=True, null=True) 

Run migrations:

python manage.py makemigrations && \
python manage.py migrate

Now you can use the wysiwyg editor in the Admin area when using the description field.

Serving Static Files In Development And Production

CKEDitor static assets (js, css) are loaded automatically when we are using the development server.

If you have django.contrib.staticfiles listed in the INSTALLED_APPS list, then Django will serve the static files automatically in DEBUG mode.

But in production, you should define STATIC_URL and STATIC_ROOT...

STATIC_URL = '/static/' 
STATIC_ROOT = '/home/mysite/static/' 

...and run python manage.py collectstatic to collect the resources to the STATIC_ROOT folder.

In Nginx you could add a these lines to the nginx configuration file to serve static and media files:

location /static/ { alias /home/mysite/static/; } 
location /media/ { alias /home/mysite/media/; }

How To Upload Files

Add ckeditor_uploader and CKEDITOR_UPLOAD_PATH to the settings file:

INSTALLED_APPS = [
    ...
    'ckeditor', 
    'ckeditor_uploader',
]

CKEDITOR_UPLOAD_PATH = "uploads/"

Include CKEditor urls in the main urls.py file:

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('base.urls')),
    # here
    path('ckeditor/', include(
        'ckeditor_uploader.urls')),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

Make sure to serve user uploaded media files in development by adding this to urlpatterns:

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

And defining these in the settings.py file:

MEDIA_URL = '/media/' 
MEDIA_ROOT = 'media/'

For file uploads we have to change the field type to RichTextUploadingField:

# description = RichTextField(blank=True, null=True) 
description = RichTextUploadingField(blank=True, null=True)

Run migrations:

python manage.py makemigrations && \
python manage.py migrate

Go to the admin area and you should be able to upload images with the description field.

Use the safe filter in templates to show all the HTML markup:

{{ post.description | safe }}

Custom Forms

Create forms.py file inside some app folder. I will be using the blog app.

Add these lines in it:

from django.forms import ModelForm 
from .models import Post

class PostForm(ModelForm): 
    class Meta: 
        model = Post
        fields = ['description'] 

Edit main urls.py file and add these lines:

from blog import views as blog_views

path('ckeditor/', include('ckeditor_uploader.urls')),
path('add/post/', blog_views.add_post, name='add_post'),
path('edit/post/<int:post_id>/', blog_views.edit_post, name='edit_post')

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

from django.shortcuts import render 
from .forms import PostForm 

def add_post(request): 
    if request.method == "POST": 
        form = PostForm(request.POST) 
        if form.is_valid(): 
            post_item = form.save(commit=False) 
            post_item.save() 
            return redirect('/') 
        else: 
            form = PostForm() 
    return render(request, 'blog/post_form.html', {'form': form})

Create the form template in /blog/templates/blog/post_form.html:

<form method="POST" action=""> 
    {% csrf_token %} 
    {{ form.media }} 
    {{ form }} 
    <input type="submit" value="Save"> 
</form>

Now you can add posts in /add/post/.

Edit main urls.py file and add this line:

from blog import views as blog_views 

path('ckeditor/', include('ckeditor_uploader.urls')),
path('add/post/', blog_views.add_post, name='add_post'),
# here
path('edit/post/<int:post_id>/', blog_views.edit_post, name='edit_post'),

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

def edit_post(request, post_id=None): 
    item = get_object_or_404(Post, id=post_id) 
    form = PostForm(request.POST or None, instance=item) 
    if form.is_valid(): 
        form.save() 
        return redirect('/') 
    return render(request, 'blog/post_form.html', {'form': form}) 

Now you can edit posts in /edit/post/[id]/.

Custom Configuration

You can customize the toolbar with CKEDITOR_CONFIGS dictionary.

Put this in the settings.py file:

CKEDITOR_CONFIGS = {'default':
                        {'toolbar': 'Custom', 'toolbar_Custom':
                            [
                                ['Bold', 'Link', 'Unlink', 'Image'], 
                            ], 
                         }}

Now by default you have just these 4 options in the toolbar.

Check the https://docs.ckeditor.com/ckeditor4/latest/api/CKEDITOR_config.html#cfg-height for more configuration options.

Here I change the height to 500 px:

CKEDITOR_CONFIGS = {'default':
                        {'toolbar': 'Custom', 'height': 500, 'toolbar_Custom':
                            [
                                ['Bold', 'Link', 'Unlink', 'Image'],
                            ],
                         }}

You can define multiple configurations like this:

CKEDITOR_CONFIGS = {
    'default':
        {'toolbar': 'Custom', 'height': 500,
         'toolbar_Custom': [
             ['Bold', 'Link', 'Unlink', 'Image'],
         ],
         },
    'special': 
        {'toolbar': 'Special', 
         'toolbar_Special': 
             [
                 ['Bold'], 
             ], 
         }
}

And then define the configuration in models.py:

description = RichTextUploadingField(blank=True, 
                                     null=True) 
# < here 
description2 = RichTextUploadingField(blank=True, 
                                      null=True, 
                                      config_name='special') 

Run migrations:

python manage.py makemigrations && \
python manage.py migrate

Now the first description field gets the default settings and description2 field gets our special configuration.

You can find the default Full toolbar in the widgets.py file:

vim venv/lib/python3.6/site-packages/ckeditor/widgets.py

Here we use the default Full toolbar options for our Custom toolbar:

CKEDITOR_CONFIGS = { 'default': 
                         { 'toolbar': 'Custom', 'height': 500, 'toolbar_Custom': 
                             [ 
                                 ['Styles', 'Format', 'Bold', 'Italic', 'Underline', 'Strike', 'SpellChecker', 'Undo', 'Redo'], 
                                 ['Link', 'Unlink', 'Anchor'], 
                                 ['Image', 'Flash', 'Table', 'HorizontalRule'], 
                                 ['TextColor', 'BGColor'], 
                                 ['Smiley', 'SpecialChar'], 
                                 ['Source'], 
                             ], 
                           }, 'special': 
                    { 'toolbar': 'Special', 'toolbar_Special': 
                            [ 
                             ['Bold'], 
                            ], 
                          } 
                     }

Extra Plugins (CodeSnippet)

The package ships with some extra plugins that are not enabled by default.

Here is an example how to enable CodeSnippet syntax highlighter:

'special': {'toolbar': 'Special', 'toolbar_Special':
                [
                    ['Bold'], ['CodeSnippet'],
                ], 'extraPlugins': 'codesnippet',
            }

Now you should have a CodeSnippet icon in the Special toolbar.

You also need some syntax highlighter. My personal favourite is https://highlightjs.org/.

Add this to your html head section:

<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/default.min.css">
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js"></script>
<script src="/static/base/js/main.js"></script>
<script>hljs.initHighlightingOnLoad();</script> 

Now you should see the syntax highlighting working when you print out the field in templates like this:

{{ post.description2 | safe }} 

Custom Plugins (Youtube)

Here is how you can add more plugins.

Use external_plugin_resources in models.py:

description2 = RichTextUploadingField(blank=True, 
                                      null=True, 
                                      config_name='special', 
                                      external_plugin_resources=[
                                          ('youtube', 
                                            '/static/base/vendor/ckeditor_plugins/youtube/youtube/', 
                                            'plugin.js', 
                                            )
                                      ], )

In this example we add the https://ckeditor.com/cke4/addon/youtube.

Download the plugin and put it in some app's static folder. In here I use the base app. I put external libraries in the vendor folder:

cd base/static/base/vendor 
mkdir -p ckeditor_plugins/youtube 
cd ckeditor_plugins/youtube 
wget https://download.ckeditor.com/youtube/releases/youtube_2.1.10.zip --no-check-certificate 
unzip youtube_2.1.10.zip 
rm youtube_2.1.10.zip

Enable the plugin in the settings.py file:

'special': { 'toolbar': 'Special', 
             'toolbar_Special': 
                [ 
                    ['Bold'], 
                    ['CodeSnippet', 'Youtube'], 
                ], 'extraPlugins': ','.join(['codesnippet', 
                                             'youtube']), }

And now you can also embed youtube videos.