DEV Community

TheHormat
TheHormat

Posted on

Using React CKEditor 5 with Django(DRF)

I wanted to share with you how I did this CKEditor problem, which I have been working on in one of my projects for a long time and finally managed to solve. Frankly, I couldn’t find enough information or a good blog on this subject. That’s why I thought of writing a small blog that will be detailed and easy for you.

Normally, it is not that difficult to use, but the image uploading part bothered me a bit. Since I was trying to do it via the API, I realized that I needed to play around with the settings a little.

So let’s see how I did it now.

Image description

Django configuration:

  1. First we need to download django rest framework:

pip install djangorestframework

#settings.py
INSTALLED_APPS = [
    ...
    "rest_framework",
    "rest_framework.authtoken",
]

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.TokenAuthentication',
        'rest_framework.authentication.BasicAuthentication',
    ]
}
Enter fullscreen mode Exit fullscreen mode
  1. Since we are using CKEditor in React, let’s make the necessary adjustments to avoid getting any CORS errors.

pip install django-cors-headers

#settings.py
INSTALLED_APPS = [
    ...,
    "corsheaders",
    ...,
]

MIDDLEWARE = [
    ...,
    "corsheaders.middleware.CorsMiddleware",
    "django.middleware.common.CommonMiddleware",
    ...,
]

#localhost:5173 my react app url
CSRF_TRUSTED_ORIGINS = ["http://localhost:5173"]
CORS_ORIGIN_WHITELIST = [
    "http://localhost:5173",
]
CORS_ORIGIN_ALLOW_ALL = False
CORS_ALLOW_CREDENTIALS = True
CORS_ALLOW_METHODS = [
    "DELETE",
    "GET",
    "OPTIONS",
    "PATCH",
    "POST",
    "PUT",
]

CORS_ALLOW_HEADERS = [
    "accept",
    "accept-encoding",
    "authorization",
    "content-type",
    "dnt",
    "origin",
    "user-agent",
    "x-csrftoken",
    "x-requested-with",
    "token",
]
Enter fullscreen mode Exit fullscreen mode
  1. Now let’s make the necessary adjustments for CKEditor. For both user and developer performance, I wanted to use CKEditor in Django itself. The adjustments we made here are to specify the path when the user uploads an image on the React side and to ensure that Django allows this.
#settings.py
INSTALLED_APPS = [
    ...,
    "ckeditor",
    "ckeditor_uploader",
    ...,
]

CKEDITOR_UPLOAD_PATH = "uploads/"
CKEDITOR_IMAGE_BACKEND = "pillow"
CKEDITOR_STORAGE_BACKEND = 'django.core.files.storage.FileSystemStorage'
CKEDITOR_CONFIGS = {
    "default": {
        "filebrowserImageUploadUrl": "/ckeditor/upload/",
    },
}


#urls.py:
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [
    path("ckeditor/upload/", uploadFile, name="ckeditor_upload"),
    path("ckeditor/", include("ckeditor_uploader.urls")),
]


urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
Enter fullscreen mode Exit fullscreen mode

We have finished our settings in Django, now let’s move on to the main part, React.

First, let’s include CKEditor-u in our project.

npm install --save @ckeditor/ckeditor5-react @ckeditor/ckeditor5-build-classic

// AddBlog.jsx
import React, { Component } from 'react';
import { CKEditor } from '@ckeditor/ckeditor5-react';
import ClassicEditor from '@ckeditor/ckeditor5-build-classic';
Enter fullscreen mode Exit fullscreen mode

Well, we can actually use CKEditor with this, but we will need to make some more adjustments to upload images.

First, let’s create a component and write the necessary codes here.

// components/CustomUploadAdapter.jsx
class CustomUploadAdapter {
  constructor(loader) {
    this.loader = loader;
    this.url = 'http://127.0.0.1:8000/ckeditor/upload/'; // Here we define the path we specified in django
  }

  upload() {
    return this.loader.file.then(file => new Promise((resolve, reject) => {
      const data = new FormData();
      data.append('upload', file);

      const token = localStorage.getItem('token');

      if (!token) {
        reject('No token found. User must be logged in to upload images.');
        return;
      }
      fetch(this.url, {
        method: 'POST',
        body: data,
        headers: {
          "Authorization": `Token ${token}`
        },

      })
        .then((response) => response.json())
        .then(response => {
          if (response.url) {
            resolve({
              default: response.url 
            });
          } else {
            reject(response.error ? response.error.message : 'An error occurred while uploading the file.');
          }
        })
        .catch(error => {
          reject('An error occurred while uploading the file: ' + error.message);
        });
    }));
  }

  abort() {
    console.log('Wasted...');
  }
}

export default CustomUploadAdapter;
Enter fullscreen mode Exit fullscreen mode

An important part of this code that you need to pay attention to is using the logged in user’s token correctly and specifying it properly. Since there is a login section on my site, these settings have been written accordingly.

Did it seem a little long? Yes, you are right, it seemed like that to me too :D

Great, now we are at the last part, now that we have made all the necessary adjustments, we can fully edit the AddBlog page.

// AddBlog.jsx
import React, { Component } from 'react';
import { CKEditor } from '@ckeditor/ckeditor5-react';
import ClassicEditor from '@ckeditor/ckeditor5-build-classic';
import CustomUploadAdapter from '../components/CustomUploadAdapter'; // Let's show the path to the Adapter code we prepared.

const AddBlogs = () => {

  const handleChange = (e) => {
    const { name, value } = e.target;
    setBlogData(prevState => ({
      ...prevState,
      [name]: value
    }));
  };

  const handleCkeditorState = (language, _event, editor) => {
    const data = editor.getData();
    setBlogData(prevState => ({
      ...prevState,
      [`${language}_content`]: data
    }));
  };

  const handleContentBeforeLoad = (content) => {
    const backendBaseURL = 'http://127.0.0.1:8000';
    return content.replace(/src="\/media\/(.*?)"/g, `src="${backendBaseURL}/media/$1"`);
  };

  const onEditorInit = (editor) => {
    editor.plugins.get('FileRepository').createUploadAdapter = (loader) => {
      return new CustomUploadAdapter(loader);
    };
  };


  return (
    <div className='addblog'>
      <h2>{id ? 'Blog Update' : 'Add new blog'}</h2>
      <form onSubmit={handleSubmit}>
        <div>
          <label htmlFor="en_content">En Content:</label> 
          <CKEditor
            editor={ClassicEditor}
            data={handleContentBeforeLoad(blogData.en_content)}
            onChange={(event, editor) => handleCkeditorState('en', event, editor)}
            onReady={onEditorInit}
            config={{
              extraPlugins: [CustomUploadAdapter],
              ckfinder: {
                uploadUrl: 'http://127.0.0.1:8000/ckeditor/upload/'
              }
            }}
          />
        </div>

        <div className="buttons">
          <button id='dashboard__submit__btn' type="submit">{id ? 'Update' : 'Add'}</button>
          {id && (
            <button id='dashboard__delete__btn' type="button" onClick={handleDelete}><i className="fa-solid fa-trash"></i></button>
          )}
        </div>
      </form>
    </div>
  );
}

export default AddBlogs;
Enter fullscreen mode Exit fullscreen mode

Conclusion
You don’t have to panic about it, you can easily find it and fix it after a detailed search. At most, you will get an error about which versions of the tools are incompatible.

Before you go… If you have any questions/suggestions/thoughts, do drop me a line below. 🖋️

And if you enjoyed this, let us know how you felt with a nice emoji(🤯❤️‍🔥) and don't forget to follow for future updates.

That’s it from me. We will talk soon!

— TheHormat ♟️

Top comments (0)