Django中如何为TinyMCE上传的图片生成Slug化链接?
解决Django+TinyMCE图片base64转Slug化媒体链接问题
一、修复前端TinyMCE配置错误
之前的报错源于CSRF令牌未处理、上传路径无效,以及旧的base64逻辑与上传处理器冲突。替换为以下正确配置:
{ "height": 500, "entity_encoding": "raw", "menubar": "file edit view insert format tools table help", "plugins": "print preview paste importcss searchreplace autolink autosave save code visualblocks visualchars fullscreen image link media template codesample table charmap hr pagebreak nonbreaking anchor toc insertdatetime advlist lists wordcount imagetools textpattern noneditable help charmap emoticons quickbars", "toolbar": "fullscreen preview | undo redo | bold italic forecolor backcolor | formatselect | image link | alignleft aligncenter alignright alignjustify | outdent indent | numlist bullist checklist | fontsizeselect emoticons | ", "custom_undo_redo_levels": 50, "quickbars_insert_toolbar": false, "images_upload_url": "/api/upload-image/", // 替换为你的Django上传接口路径 "images_upload_handler": function(blobInfo, success, failure) { var xhr, formData; xhr = new XMLHttpRequest(); xhr.withCredentials = true; xhr.open("POST", "/api/upload-image/"); // 注入Django要求的CSRF令牌 xhr.setRequestHeader("X-CSRFToken", getCookie("csrftoken")); xhr.onload = function() { var json; if (xhr.status !== 200) { failure("HTTP错误: " + xhr.status); return; } json = JSON.parse(xhr.responseText); if (!json || typeof json.location !== "string") { failure("无效JSON响应: " + xhr.responseText); return; } success(json.location); }; xhr.onerror = function() { failure("上传请求失败,请检查网络或后端服务"); }; formData = new FormData(); formData.append("file", blobInfo.blob(), blobInfo.filename()); xhr.send(formData); }, "content_style": "body { font-family:Roboto,Helvetica,Arial,sans-serif; font-size:14px }" } // 辅助函数:获取页面CSRF Cookie function getCookie(name) { let cookieValue = null; if (document.cookie && document.cookie !== '') { const cookies = document.cookie.split(';'); for (let i = 0; i < cookies.length; i++) { const cookie = cookies[i].trim(); if (cookie.substring(0, name.length + 1) === (name + '=')) { cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); break; } } } return cookieValue; }
二、搭建Django后端上传接口
1. 配置媒体存储(settings.py)
import os from pathlib import Path BASE_DIR = Path(__file__).resolve().parent.parent # 媒体文件存储配置 MEDIA_ROOT = BASE_DIR / 'media' MEDIA_URL = '/media/' INSTALLED_APPS = [ # ...其他已安装应用 'django.contrib.staticfiles', 'pillow', # 需提前安装:pip install pillow ]
2. 配置媒体访问路由(urls.py)
from django.conf import settings from django.conf.urls.static import static from django.urls import path from . import views urlpatterns = [ # ...其他业务路由 path('api/upload-image/', views.upload_image, name='upload-image'), ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
3. 编写上传处理视图(views.py)
import os import uuid from django.http import JsonResponse from django.views.decorators.csrf import csrf_exempt from django.utils.text import slugify from PIL import Image from django.conf import settings def generate_slugged_filename(filename): # 生成Slug化文件名+唯一ID,强制转换为webp格式 name, _ = os.path.splitext(filename) slugged_name = slugify(name) unique_id = str(uuid.uuid4())[:8] return f"{slugged_name}-{unique_id}.webp" @csrf_exempt def upload_image(request): if request.method != 'POST': return JsonResponse({'error': '仅支持POST请求'}, status=400) if 'file' not in request.FILES: return JsonResponse({'error': '未上传文件'}, status=400) file = request.FILES['file'] # 验证图片类型 allowed_types = ['image/jpeg', 'image/png', 'image/gif'] if file.content_type not in allowed_types: return JsonResponse({'error': '仅支持JPG/PNG/GIF格式'}, status=400) # 生成存储路径 filename = generate_slugged_filename(file.name) save_dir = os.path.join(settings.MEDIA_ROOT, 'images') os.makedirs(save_dir, exist_ok=True) save_path = os.path.join(save_dir, filename) # 转换为webp并保存 try: with Image.open(file) as img: # 处理透明图片背景 if img.mode in ('RGBA', 'LA'): background = Image.new('RGB', img.size, (255, 255, 255)) background.paste(img, mask=img.split()[-1]) img = background img.save(save_path, 'WEBP', quality=85) except Exception as e: return JsonResponse({'error': f'图片处理失败: {str(e)}'}, status=500) # 返回可访问的媒体URL media_url = f"{settings.MEDIA_URL}images/{filename}" # 生产环境替换为绝对域名: # media_url = f"https://www.endustri.io{settings.MEDIA_URL}images/{filename}" return JsonResponse({'location': media_url})
三、测试与验证
- 重启Django服务,进入TinyMCE编辑器上传图片,检查生成的链接是否为
/media/images/xxx-slugged-name.webp格式。 - 发布文章后,确认页面加载的是媒体文件链接而非base64编码。
四、生产环境注意事项
- 确保
MEDIA_ROOT目录拥有服务器写入权限。 - 用Nginx等反向代理处理媒体文件访问,禁用Django自带的静态文件服务。
- 若使用云存储(如S3、OSS),修改
settings.py的DEFAULT_FILE_STORAGE为对应后端,视图逻辑无需改动。
内容的提问来源于stack exchange,提问作者Prusa
相关产品推荐
相关产品推荐

