Integrating CKeditor in Django

At this point we are using a simple 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 espcaping.

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 or Power Point. 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. In our project we are going to use CKEditor.

Fortunately, there is package called Django CKEditor, which allows us to integrate CKEditor with 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\C\TGDB-V2\django_project>pip install django-ckeditor
Collecting django-ckeditor
Requirement already satisfied: Django in c:\users\c\tgdb-v2\env\lib\site-package
s (from django-ckeditor)
Installing collected packages: django-ckeditor
Successfully installed django-ckeditor-5.2.2

(env) C:\Users\C\TGDB-V2\django_project>

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

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:

####################################
    ##  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 created thumbnails CKEditor gallery.

CKEDITOR_CONFIGS refer to 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\C\TGDB-V2\django_project>pip install pillow
Collecting pillow
  Using cached Pillow-4.1.1-cp34-cp34m-win_amd64.whl
Collecting olefile (from pillow)
Installing collected packages: olefile, pillow
Successfully installed olefile-0.44 pillow-4.1.1

(env) C:\Users\C\TGDB-V2\django_project>

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

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 (with no image uploading capability) while RichTextUploadingField field adds a rich text editor with image uploading capability.

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

Just like model field Django CKEditor provides CKEditorWidget and CKEditorUploadingWidget which adds rich text editor with no image uploading capability and a rich text editor with image uploading capability respectively.

To use CKEditorWidget or CKEditorUploadingWidget pass widget=CKEditorWidget() or widget=CKEditorUploadingWidget() to the the CharField(). For example:

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 essential Javascript files used by CKEditor are in place. One way to achieve it is to use form.media variable to import all the necessary files automatically.

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

or you can even manually load individual files 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 should have a good understaning of django-ckeditor library. 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().

...
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()
    ...

Visit Django Admin and Click on the link to create new post. You will represented with page that looks like this:

[img]

We are seeing is Ckeditor is action. Now you have full capability to format posts to your hearts content. 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:

[img]

Click Browse Server, this will open CKEditor Image Gallery which allows us to view all your previously uploaded images.

[img]

To use image select it and click on "Embed Image" button at the bottom of the window.

Alternatively, you upload a new image from your hard drive. To upload image click on the Upload tab.

[]

Select file and Click "Send it to the Server".

[]

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". To view the post click on "VIEW ON SITE" link at the top right corner of the page and you see a page like this:

[]

What's going on ?

As we already know by default Django escapes HTML because of security concerns. To tell Django not to escape data use the safe filter. Open post_details.html in the blog directory and change post.content|linebreaks to post.content|safe as follows:

post_detail.html

...
</p>

    {{ post.content|safe }}

</div>
...

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

[img]

Adding CkEditor media files to cadmin

At this point CKEditor is working as Expected in Django admin. But if you login to cadmina and click on Add Post, you will find that CKEditor is not showing up.

[img]

The same problem exists in post update page.

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 form templates.

Open post_add.html and post_update.html and update the code as follows:

post_add.html

       ...
        <form action="{% url 'cadmin:post_add' %}" method="post">
            {% csrf_token %}
            {{ form.media }}
            <table>            
            ...
        </form>
        ...

post_update.html

        ...
        <form action="{% url 'cadmin:post_update' post.id %}" method="post">
            {{ form.media }}
            {% csrf_token %}
            <table>        
             ...
        </form>                    
        ...

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

[img]

By Default Django CKEditor only allows staff members (i.e is_staff=True) to upload and browse images. Login to cadmin using a non-staff account, then visit Add Post page and Click on Browser Server. You will get a page like this:

[img]

Here Django is asking you to upload image , you have to login with a different account, the one with staff privilge.

There are two ways to solve this problem:

1) set is_staff attribute to True every time a new user is created

2) Change the source code if django-ckeditor to allow every registered user to upload image irrespective of the value of is_staff attribute.

Lets start with the first one first.

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

...
            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 make an existing user a staff member, type the following commands in terminal or command prompt.

>>>
>>> from django.contrib.auth.models import User
>>> user = User.objects.get(username='nonstaff')
>>>
>>> user.is_staff
False
>>>
>>> user.is_staff = True
>>>
>>> user.save()
>>>
>>> user.is_staff
True
>>>

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

It is important to note that setting is_staff attribute to True allows users to login to Django Admin but doesn't give any extra permission to the user. The user will not be able to perform any action in the Django Admin at all. To verify this fact login to cadmin using a non-staff account. Then visit Django Admin i.e http://127.0.0.1:8000/admin/. You will get a page like this:

[img]

As you can see the logged in user don't have permission to edit anything, which is what we want. On the other hand in the Custom admin panel 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.