Integrating CKeditor in Django

At this point, we are using HTML <textarea> element to create content. It has following drawbacks:

  1. We are unable to format content using HTML.
  2. No support for images.

On way to solve these problem is to type HTML code directly into the <textarea> element and then use safe filter in template. Recall that by default, for security reasons Django escapes the content of the variable, the safe filter tells Django that content is safe and does not require HTML escaping.

But the problem is nobody likes to type HTML. Further, from the usability point of view our site would be useless for users who don't know HTML. That is unacceptable.

What we need is a WYSWIYG HTML editor.

WYSWIYG ("what you see is what you get") HTML editor allows to format content easily by providing an interface similar for Microsoft Word. To use a WYSWIYG editor just select the formatting you want and editor will automatically create valid HTML markup.

There are many WYSWIYG editors out there like TinyMCE, Froala, CKEditor etc. In our project, we are going to use CKEditor.

Fortunately, there is package called Django CKEditor, which allows us to integrate CKEditor into Django easily.

After integrating Django CKEditor we will have a Rich Text Editor with image uploading capability instead if boring <textarea> element.

Installing Django CKEditor #

In terminal or command prompt and enter the following command to install Django CKEditor.

(env) C:\Users\Q\TGDB\django_project>pip install django-ckeditor
Collecting django-ckeditor
  Downloading django-ckeditor-5.3.0.tar.gz (1.6MB)
    100% |################################| 1.6MB 108kB/s
Collecting django-js-asset (from django-ckeditor)
  Downloading django_js_asset-0.1.1-py2.py3-none-any.whl
Building wheels for collected packages: django-ckeditor
  Running setup.py bdist_wheel for django-ckeditor ... done
  Stored in directory: C:\Users\X\AppData\Local\pip\Cache\wheels\37\60\7b\2d8074
6aeb483a5993e668475ef7dc9c9f0ae7fed109cb6eea
Successfully built django-ckeditor
Installing collected packages: django-js-asset, django-ckeditor
Successfully installed django-ckeditor-5.3.0 django-js-asset-0.1.1

(env) C:\Users\Q\TGDB\django_project>

Open settings.py file and add 'ckeditor' and 'ckeditor_uploader' to INSTALLED_APPS list as follows:

TGDB/django_project/django_project/settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django.contrib.flatpages',
    'django.contrib.sites',
    'django.contrib.sitemaps',
    'ckeditor',
    'ckeditor_uploader',
    'blog',
    'cadmin'
]

At the end of settings.py file add the following configuration:

TGDB/django_project/django_project/settings.py

####################################
    ##  CKEDITOR CONFIGURATION ##
####################################

CKEDITOR_JQUERY_URL = 'https://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js'

CKEDITOR_UPLOAD_PATH = 'uploads/'
CKEDITOR_IMAGE_BACKEND = "pillow"

CKEDITOR_CONFIGS = {
    'default': {
        'toolbar': None,
    },
}

###################################

Here is a rundown of each property we have used above:

CKEDITOR_JQUERY_URL refers to the jquery file which CKEditor uses.

CKEDITOR_UPLOAD_PATH refers the directory where images will be uploaded relative to your MEDIA_ROOT. Recall the we already set MEDIA_ROOT to os.path.join(BASE_DIR, 'media'). So the image files will be uploaded to media/uploads directory. You don't need to create uploads directory it will be created automatically for you.

CKEDITOR_IMAGE_BACKEND refers to the image library which CKEditor uses to create thumbnails to display in CKEditor gallery.

CKEDITOR_CONFIGS refer to the settings which CKEditor uses to customize its appearance and behavior. To learn more about settings visit CKEditor configuration (http://docs.ckeditor.com/#!/guide/dev_configuration).

Before we go ahead further, lets install pillow library which django-ckeditor will use to create thumbnails.

(env) C:\Users\Q\TGDB\django_project>pip install pillow
Collecting pillow
  Downloading Pillow-4.2.1-cp34-cp34m-win_amd64.whl (1.4MB)
    100% |################################| 1.4MB 112kB/s
Collecting olefile (from pillow)
Installing collected packages: olefile, pillow
Successfully installed olefile-0.44 pillow-4.2.1

(env) C:\Users\Q\TGDB\django_project>

At last, add ckeditor URLs to django project's urls.py file.

TGDB/django_project/django_project/urls.py

...
urlpatterns = [
    url(r'', include('blog.urls')),
    url(r'^admin/', admin.site.urls),
    url(r'^cadmin/', include('cadmin.urls')),
    url(r'^ckeditor/', include('ckeditor_uploader.urls')),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

Django CKEditor Usage #

Django CKEditor provides the following two ways to integrate Rich Text Editor:

  1. Using Model Fields.
  2. Using Form Widgets.

Model Fields #

There are two type of model fields RichTextField and RichTextUploadingField. The RichTextField only adds a rich text editor without image uploader while RichTextUploadingField field adds a rich text editor with image uploader.

To use RichTextField or RichTextUploadingField replace models.TextField() with RichTextField or RichTextUploadingField field. For example:

from ckeditor_uploader.fields import RichTextField, RichTextUploadingField

class ModelClass:
    ## content = models.TextField()
    content = RichTextUploadingField()

Form widgets #

Django CKEditor also provides CKEditorWidget and CKEditorUploadingWidget widgets which adds rich text editor without image uploader and a rich text editor with image uploader respectively.

To use them, just pass CKEditorWidget or CKEditorUploadingWidget to the widget keyword argument as follows:

from ckeditor_uploader.widgets import CKEditorWidget, CKEditorUploadingWidget

class PostForm(forms.ModelForm):
    content = forms.CharField(widget=CKEditorUploadingWidget())

Rendering CKEditor outside of Django Admin #

It doesn't matter which method you use if you are rendering CKEditor outside of Django Admin you will have to make sure all the necessary JavaScript files used by CKEditor are available in your templates. One way to achieve this is to use form.media variable in your template to import all the necessary files automatically.

<form>
    {{ form.media }}
    {{ form.as_p }}
    <input type="submit"/>
</form>

or load individual files manually as follows:

{% load static %}
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
<script type="text/javascript" src="{% static "ckeditor/ckeditor-init.js" %}"></script>
<script type="text/javascript" src="{% static "ckeditor/ckeditor/ckeditor.js" %}"></script>

Now you should have a good understanding of django-ckeditor package. Lets integrate it in our project.

Updating Model Fields #

Open blog's app models.py file. In the Post model class change the type of content field from models.TextField() to RichTextUploadingField() as follows.

TGDB/django_project/blog/models.py

...
from ckeditor_uploader.fields import RichTextUploadingField
...

class Post(models.Model):
    title = models.CharField(max_length=200)
    slug = models.SlugField(max_length=200, help_text="Slug will be generated automatically from the title of the post")
    # content = models.TextField()
    content = RichTextUploadingField()
    ...

In the Django Admin, visit Add post/Change post page and you will represented with page that looks like this:

django-ckeditor.png

We are seeing is Ckeditor is action. Now you have full capability to format posts to your hearts content. Add some paragraphs of text and heading. To add images to post click on image icon on the left side, just above "Styles" dropdown and you will see a pop-up window like this:

click-to-upload-image.png

django-ckeditor-image-uploader.png

Click the "Browse Server" button, this will open CKEditor Image Gallery which allows us to view uploaded images as well as add images into the post. To add an image into the post, select it and click on "Embed Image" button at the bottom of the window.

ckeditor-image-gallery.png

In case, you haven't uploaded any images, then CKEditor Image Gallery will look like this:

ckeditor-empty-image-gallery.png

We can also upload image from the hard drive. To do so, click on the "Upload" tab. Select file and Click "Send it to the Server".

ckeditor-uploading-image.png

You will be taken back to Images Info tab. Click OK to add image to the post. Save the post by clicking "Save and continue editing".

image-info-tab.png

embedded-image.png

To view the post click on the "VIEW ON SITE" link at the top right corner of the page and you see a page like this:

escaped-post-data.png

What's going on ?

As we already know, by default, Django escapes HTML because of security concerns. We can tell Django not to escape data by using safe filter. Open post_details.html in the blog directory and change post.content|linebreaks to post.content|safe.

TGDB/django_project/blog/templates/blog/post_detail.html

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

Revisit the post we created above. This time you should see properly formatted HTML as follows:

properly-formatted-html-JYVZTK.png

Adding CKEditor Media Files to cadmin #

At this point, CKEditor is working as Expected in Django Admin. But if you login to cadmin and visit on Add Post/Update Post page, you will find that CKEditor is not showing up.

ckeditor-now-showing-up-in-cadmin.png

The problem is that necessary JavaScript files used by CkEditor are not available in our custom admin panel. To add these files, we have to use form.media template variable to our templates.

Open cadmin's, post_add.html and post_update.html template and update the code as follows:

TGDB/django_project/cadmin/templates/cadmin/post_add.html

        ...
        <form action="" method="post">
            {% csrf_token %}
            {{ form.media }}
            <table>            
            ...
        </form>
        ...

TGDB/django_project/cadmin/templates/cadmin/post_update.html

        ...
        <form action="" method="post">
            {{ form.media }}
            {% csrf_token %}
            <table>        
            ...
        </form>                    
        ...

Visit Add Post and Post Update page. This time CKEditor should be visible in both the pages.

ckeditor-in-cadmin-RXVHBA.png

By Default, Django CKEditor only allows staff members (i.e is_staff=True) to upload and browse images. All the users created via http://127.0.0.1:8000/cadmin/register/ have is_staff attribute set to False, which means that they can't upload or browse images using CKEditor. If you try to browser or upload image using a non-superuser account in cadmin you will get a page like this

ckeditor-change-account-to-upload-image.png

As you can see, Django is asking you to login with a different account, the one with staff privilege i.e is_staff=True.

There are two ways to solve this problem:

  1. set is_staff attribute to True every time a new user is created (recommended approach).

  2. Change the source code if django-ckeditor package to allow every registered user to upload image irrespective of the value of is_staff attribute (not recommended because it will break you application after every update).

Let's start with the first one.

To set is_staff to True, open cadmin's views.py, and update register() view as follows:

TGDB/django_project/cadmin/views.py

...
            if not error_email_verification:
                u = User.objects.create_user(
                        request.POST['username'],
                        request.POST['email'],
                        request.POST['password1'],
                        is_active = 0,
                        is_staff = True
                )
...

This update will only be applied to newly created users. To update the is_staff attribute of all existing user created via Create User Form (i.e http://127.0.0.1:8000/cadmin/register/) execute the following commands in Django shell:

>>>
>>> authors = Author.objects.filter(activation_key__isnull=False)
>>>
>>> for a in authors:
...   a.user.is_staff = True
...   a.user.save()
...
>>>

You could also use Django Admin to do the above task.

It is important to note that setting is_staff attribute to True allows non-superusers to login into Django Admin but doesn't give any extra permission to do anything. In other words, after logging in the user will not be able to perform any action in the Django Admin at all. To verify this fact login to Django Admin (http://127.0.0.1:8000/admin/) using non-superuser account and you will be displayed a page like this:

no-permission.png

As you can see, the logged in user don't have permission to perform any action, which is what we want. On the other hand, in the cadmin he can now upload images and edit anything object which he created himself.

Another way to solve this problem is to change the source code of django-ckeditor. By default django-ckeditor uses staff_member_required decorator which checks whether the logged in user is staff member of not. staff_member_required only triggers the views when logged in user is staff member. To allow any logged in user to browse and upload images replace staff_member_required with login_required.

...
from django.contrib.admin.views.decorators import staff_member_required, login_required

if django.VERSION >= (1, 8):
    urlpatterns = [

        # Custom
        #
        url(r'^upload/', login_required(views.upload), name='ckeditor_upload'),
        url(r'^browse/', login_required(views.browse), name='ckeditor_browse'),

        # Default
        #        
        # url(r'^upload/', staff_member_required(views.upload), name='ckeditor_upload'),
        # url(r'^browse/', never_cache(staff_member_required(views.browse)), name='ckeditor_browse'),
    ]
...

You should not change the original code directy instead make a copy and comment out the original code and then make changes on the copy as required.

Thats all there is to it. You should now have a pretty good understanding of Django Framework. I hope you enjoyed it.