備忘録　django-ckeditorの環境構築

## はじめに
django-ckeditorを触る機会があり組み込む作業で四苦八苦したので、簡単な検証環境を作り具体的な作業の流れを備忘録として残す

## 検証環境
本記事では仮想環境、ckeditorで検証している
```
 python -m venv ckeditor 
.\ckeditor\Scripts\activate  
```

django、django-ckeditor、pillowをあらかじめpip installしておく
(pillowは画像ファイルを扱うために必要)
```
PS C:\Python_Django_data\django_ckeditor_test\django_ckeditor_test> python -V
Python 3.13.0

pip install django
pip install django-ckeditor
pip install pillow

(ckeditor) PS C:\Python_Django_data\django_ckeditor_test> pip list
Package         Version
--------------- -------
asgiref         3.8.1
Django          5.1.5
django-ckeditor 6.7.2
django-js-asset 3.0.1
pillow          11.1.0
pip             24.2
sqlparse        0.5.3
tzdata          2025.1
```

## 最初に通常のTextFieldをhtmlに表示させる
### django-ckeditorに対応させるためのbodyフィールドを作成する

```models.py
class Test(models.Model):
    body = models.TextField(max_length=2000, verbose_name="本文")
```

```forms.py
class TestForm(ModelForm):
        body = forms.TextInput()
```

```views.py
class TestCreateView(View):
    def get(self, request):
        form = TestForm()
        return render(request, "testapp/form.html", {"form": form})
```

```html
<!DOCTYPE html>
<html lang = "ja">
    <head>
        <meta chatset="utf-8"/>
        <title>CKEditor test</title>
    </head>
    <body>
        <h1>CKEditor test</h1>
        <form method = "POST">
            {% csrf_token %}
            {{ form.as_p }}
        </form>
    </body>
</html>
```

### html上のbody項目の状態
![WS000000.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3989656/524a882b-62c4-9502-6673-387305900b52.png)

## django-ckeditorを設定する
### settings.pyにckeditorを追記する
```settings.py
INSTALLED_APPS = [
    '～',
    'ckeditor',
]
```

### models.pyでフィールドタイプを変更する
```models.py
from ckeditor.fields import RichTextField
class Test(models.Model):
    body = RichTextField(max_length=2000, verbose_name="本文")
```

### forms.pyでwidgetを指定する
（エラーが出るので調べたらCharFieldにする必要があるとのこと）
```forms.py
from ckeditor.widgets import CKEditorWidget
class TestForm(ModelForm):
        body = forms.CharField(widget=CKEditorWidget)
```

### htmlの変更
```html
{% load static %}
<!DOCTYPE html>
<html lang = "ja">
    <head>
        <meta chatset="utf-8"/>
        <link href="{% static 'ckeditor/ckeditor.css' %}" media="all" rel="stylesheet">
        <script src="{% static 'ckeditor/ckeditor-init.js' %}" data-ckeditor-basepath="{% static 'ckeditor/ckeditor/' %}" id="ckeditor-init-script"></script>
        <script src="{% static 'ckeditor/ckeditor/ckeditor.js' %}"></script>
        <script src="{% static 'ckeditor/fixups.js' %}"></script>
        <title>CKEditor test</title>
    </head>
    <body>
        <h1>CKEditor test</h1>
        <form method = "POST">
            {% csrf_token %}
            {{ form.as_p }}
        </form>
    </body>
</html>
```
staticの部分はsettings.pyでINSTALLED_APPSにckeditor追記しため
C:\Python_Django_data\django_ckeditor_test\ckeditor\Lib\site-packages\ckeditor\static
を読んでいる
(その下にckeditor/ckeditor/ckeditor.js等が存在する)

### モデルを変更したため再マイグレーション
```
python manage.py makemigrations
python manage.py migrate
```

### html上のbody項目の状態
これでいくつかの項目は使用できる
![WS000001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3989656/7d406a96-230b-fab6-3560-0b95777c4a8a.png)

## 画像アップロードとフォントサイズの項目を表示させる
### settings.pyへの追記
INSTALLED_APPS への'ckeditor_uploader'の追記
``` settings.py
INSTALLED_APPS = [
    '～',
    'ckeditor_uploader',
]
```

設定の追記
``` settings.py
CKEDITOR_UPLOAD_PATH = 'uploads/'  # ここでは 'uploads/' を指定
CKEDITOR_ALLOW_NONIMAGE_FILES = False  # アップロードを画像のみに制限
CKEDITOR_IMAGE_BACKEND = "pillow"  #pip installした"pillow"を指定

MEDIA_ROOT = os.path.join(BASE_DIR, 'media')  # メディアファイルを保存するディレクトリ
MEDIA_URL = '/media/'  # メディアファイルにアクセスするためのURL
```
CKEDITOR_CONFIGSの追記
https://django-ckeditor.readthedocs.io/en/latest/ より
``` settings.py
CKEDITOR_CONFIGS = {
    'default': {
        'skin': 'moono',
        # 'skin': 'office2013',
        'toolbar_Basic': [
            ['Source', '-', 'Bold', 'Italic']
        ],
        'toolbar_YourCustomToolbarConfig': [
            {'name': 'document', 'items': ['Source', '-', 'Save', 'NewPage', 'Preview', 'Print', '-', 'Templates']},
            {'name': 'clipboard', 'items': ['Cut', 'Copy', 'Paste', 'PasteText', 'PasteFromWord', '-', 'Undo', 'Redo']},
            {'name': 'editing', 'items': ['Find', 'Replace', '-', 'SelectAll']},
            {'name': 'forms',
             'items': ['Form', 'Checkbox', 'Radio', 'TextField', 'Textarea', 'Select', 'Button', 'ImageButton',
                       'HiddenField']},
            '/',
            {'name': 'basicstyles',
             'items': ['Bold', 'Italic', 'Underline', 'Strike', 'Subscript', 'Superscript', '-', 'RemoveFormat']},
            {'name': 'paragraph',
             'items': ['NumberedList', 'BulletedList', '-', 'Outdent', 'Indent', '-', 'Blockquote', 'CreateDiv', '-',
                       'JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock', '-', 'BidiLtr', 'BidiRtl',
                       'Language']},
            {'name': 'links', 'items': ['Link', 'Unlink', 'Anchor']},
            {'name': 'insert',
             'items': ['Image', 'Flash', 'Table', 'HorizontalRule', 'Smiley', 'SpecialChar', 'PageBreak', 'Iframe']},
            '/',
            {'name': 'styles', 'items': ['Styles', 'Format', 'Font', 'FontSize']},
            {'name': 'colors', 'items': ['TextColor', 'BGColor']},
            {'name': 'tools', 'items': ['Maximize', 'ShowBlocks']},
            {'name': 'about', 'items': ['About']},
            '/',  # put this to force next toolbar on new line
            {'name': 'yourcustomtools', 'items': [
                # put the name of your editor.ui.addButton here
                'Preview',
                'Maximize',

            ]},
        ],
        'toolbar': 'YourCustomToolbarConfig',  # put selected toolbar config here
        # 'toolbarGroups': [{ 'name': 'document', 'groups': [ 'mode', 'document', 'doctools' ] }],
        # 'height': 291,
        # 'width': '100%',
        # 'filebrowserWindowHeight': 725,
        # 'filebrowserWindowWidth': 940,
        # 'toolbarCanCollapse': True,
        # 'mathJaxLib': '//cdn.mathjax.org/mathjax/2.2-latest/MathJax.js?config=TeX-AMS_HTML',
        'tabSpaces': 4,
        'extraPlugins': ','.join([
            'uploadimage', # the upload image feature
            # your extra plugins here
            'div',
            'autolink',
            'autoembed',
            'embedsemantic',
            'autogrow',
            # 'devtools',
            'widget',
            'lineutils',
            'clipboard',
            'dialog',
            'dialogui',
            'elementspath'
        ]),
    }
}
```

### models.pyでフィールドタイプをアップロードに対応させる
```models.py
from ckeditor_uploader.fields import RichTextUploadingField
class Test(models.Model):
    body = RichTextUploadingField(max_length=2000, verbose_name="本文")
```

### urls.pyの変更
プロジェクトとアプリケーション、両方のurls.pyを変更する必要がある
```urls.py
from django.conf import settings
from django.conf.urls.static import static
from django.urls import include, path
from . import views

app_name = "testapp"
urlpatterns = [
    path("test/create/", views.test_create, name="test_create"),
    path('ckeditor/', include('ckeditor_uploader.urls')),
]

urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
```
下記行を追記する必要がある
```urls.py
    path('ckeditor/', include('ckeditor_uploader.urls')),
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
```

### モデルを変更したため再マイグレーション
```
python manage.py makemigrations
python manage.py migrate
```

### html上のbody項目の状態
settings.pyのCKEDITOR_CONFIGで設定した項目が表示され、フォントサイズが変更可能になっている。
![WS000003.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3989656/8bae79b9-616b-e6c6-415f-537af558cc5d.png)

右のイメージアイコンから画像のプロパティを開きアップロードタブから画像ファイルをアップロードできる
![WS000004.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3989656/027e3c48-4b0b-7fcc-fb46-c4f1f8ae231d.png)

画像が表示される
![WS000005.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3989656/bd1f5e19-ca93-f9eb-de8a-ad086703bb24.png)

アップロードした画像ファイルは プロジェクトフォルダ\media\uploads\yyyy\mm\dd に格納される
![WS000006.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3989656/e6cdf181-7aea-4541-4359-5c0de2df4aab.png)