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: