You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django集成django-ckeditor-5前端图片上传404问题求助

Django集成django-ckeditor-5图片上传前端失效问题
  • 异常表现:前端页面上传图片时弹出提示Couldn't upload file.,终端返回404错误:Not Found: /ckeditor5/image_upload/ [18/Dec/2023 15:00:22] "POST /ckeditor5/image_upload/ HTTP/1.1" 404 4276
  • 特殊情况:Django admin后台中该功能可正常使用,仅前端页面失效

解决建议

  • 检查URL路由层级:确认项目根urls.py是否正确包含django_ckeditor_5的路由,而非仅在某个app的urls中配置。admin能正常使用说明路由在admin上下文生效,但前端页面可能因路由前缀问题导致404。
  • 验证CSRF令牌传递:CKEditor5的图片上传是异步请求,需确保实例能获取到CSRF令牌。可在CKEDITOR_5_CONFIGS的extends配置中添加:
    "csrfToken": "{{ csrf_token }}",
    
    或在模板中通过JS传递:
    document.addEventListener('DOMContentLoaded', function() {
        CKEDITOR.ClassicEditor.create(document.querySelector('.django_ckeditor_5'), {
            csrfToken: '{{ csrf_token }}'
        });
    });
    
  • 排查自定义存储类:临时注释CKEDITOR_5_FILE_STORAGE = "meetingapp.storage.CustomStorage"配置,使用默认存储测试是否恢复正常,确认自定义存储类是否实现了所有必要方法。
  • 确认表单enctype属性:通过浏览器开发者工具检查渲染后的表单是否保留enctype="multipart/form-data"属性,该属性是文件上传的必要条件。
  • 检查MEDIA配置:确认settings.py中MEDIA_ROOT和MEDIA_URL配置正确,开发环境需保留static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT),生产环境需配置服务器(如Nginx)处理媒体文件请求。

相关代码

meetingapp/models.py

class Meeting(models.Model):
    meeting_minuit_last = CKEditor5Field(config_name='extends', null=True, blank=True)

meetingapp/templates/detail.html

{% extends 'base.html' %}
{% load static %}
{% load crispy_forms_tags %}
{% load humanize %}
{% block content %}

<link rel="stylesheet" href="{% static 'css/content-styles.css' %}" type="text/css">
<link rel="stylesheet" type="text/css" href="{% static 'css/meetingapp/detail.css' %}">

<div class="meeting_minuit_main mt-5 mb-5 container">
    <h2>Meeting Minutes</h2>
    <div class="ck-content meeting-minuit mt-5 mb-5 px-5">
        {% if target_meeting.meeting_minuit_last %}
            <p>{{ target_meeting.meeting_minuit_last | safe }}</p>
        {% endif %}
    </div>
    <div>
        <form method="POST" enctype="multipart/form-data">
            {% csrf_token %}
            {% if not target_meeting.meeting_minuit_last %}
                {{ form.media }}
                {{ form.as_p }}
                <input class="btn btn-outline-secondary" type="submit" value="Submit">
            {% endif %}
            {% if target_meeting.meeting_minuit_last %}
                <a href="{% url 'meetingapp:minuit_update' pk=target_meeting.pk %}" class="btn btn-outline-primary">Update Minuit</a>
            {% endif %}
        </form>
    </div>
</div>

settings.py(CKEditor5配置部分)

customColorPalette = [
    {"color": "hsl(4, 90%, 58%)", "label": "Red"},
    {"color": "hsl(340, 82%, 52%)", "label": "Pink"},
    {"color": "hsl(291, 64%, 42%)", "label": "Purple"},
    {"color": "hsl(262, 52%, 47%)", "label": "Deep Purple"},
    {"color": "hsl(231, 48%, 48%)", "label": "Indigo"},
    {"color": "hsl(207, 90%, 54%)", "label": "Blue"},
]

CKEDITOR_5_FILE_STORAGE = "meetingapp.storage.CustomStorage"

CKEDITOR_5_CONFIGS = {
    "default": {
        "toolbar": [
            "heading",
            "|",
            "bold",
            "italic",
            "link",
            "bulletedList",
            "numberedList",
            "blockQuote",
            "imageUpload"
        ],
    },
    "comment": {
        "language": {"ui": "en", "content": "en"},
        "toolbar": [
            "heading",
            "|",
            "bold",
            "italic",
            "link",
            "bulletedList",
            "numberedList",
            "blockQuote",
        ],
    },
    "extends": {
        "language": "en",
        "blockToolbar": [
            "paragraph",
            "heading1",
            "heading2",
            "heading3",
            "|",
            "bulletedList",
            "numberedList",
            "|",
            "blockQuote",
        ],
        "toolbar": [
            "heading",
            "codeBlock",
            "|",
            "outdent",
            "indent",
            "|",
            "bold",
            "italic",
            "link",
            "underline",
            "strikethrough",
            "code",
            "subscript",
            "superscript",
            "highlight",
            "|",
            "bulletedList",
            "numberedList",
            "todoList",
            "|",
            "blockQuote",
            "insertImage",
            "|",
            "fontSize",
            "fontFamily",
            "fontColor",
            "fontBackgroundColor",
            "removeFormat",
            "insertTable",
            "sourceEditing",
        ],
        "image": {
            "toolbar": [
                "imageTextAlternative",
                "|",
                "imageStyle:alignLeft",
                "imageStyle:alignRight",
                "imageStyle:alignCenter",
                "imageStyle:side",
                "|",
                "toggleImageCaption",
                "|"
            ],
            "styles": [
                "full",
                "side",
                "alignLeft",
                "alignRight",
                "alignCenter",
            ],
        },
        "table": {
            "contentToolbar": [
                "tableColumn",
                "tableRow",
                "mergeTableCells",
                "tableProperties",
                "tableCellProperties",
            ],
            "tableProperties": {
                "borderColors": customColorPalette,
                "backgroundColors": customColorPalette,
            },
            "tableCellProperties": {
                "borderColors": customColorPalette,
                "backgroundColors": customColorPalette,
            },
        },
        "heading": {
            "options": [
                {
                    "model": "paragraph",
                    "title": "Paragraph",
                    "class": "ck-heading_paragraph",
                },
                {
                    "model": "heading1",
                    "view": "h1",
                    "title": "Heading 1",
                    "class": "ck-heading_heading1",
                },
                {
                    "model": "heading2",
                    "view": "h2",
                    "title": "Heading 2",
                    "class": "ck-heading_heading2",
                },
                {
                    "model": "heading3",
                    "view": "h3",
                    "title": "Heading 3",
                    "class": "ck-heading_heading3",
                },
            ]
        },
        "list": {
            "properties": {
                "styles": True,
                "startIndex": True,
                "reversed": True,
            }
        },
        "htmlSupport": {
            "allow": [
                {"name": "/.*/", "attributes": True, "classes": True, "styles": True}
            ]
        },
    },
}

urls.py

urlpatterns = [
    path("ckeditor5/", include('django_ckeditor_5.urls')),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

meetingapp/forms.py

class MeetingCreateDocs(ModelForm):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields["meeting_minuit_last"].required = False

    class Meta:
        model = Meeting
        fields = ['meeting_minuit_last']
        labels = {
            'meeting_minuit_last': '',
        }
        widgets = {
            "meeting_minuit_last": CKEditor5Widget(
                attrs={"class": "django_ckeditor_5"}, config_name="extends"
            )
        }

内容的提问来源于stack exchange,提问作者Won Jun Jang

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 18:25:08