获取UEditor
从 官网下载UEditor源码,下载的源码中并没有ueditor.all.js文件。需要使用grunt来把源码包打包成部署版本(含有ueditor.all.js 文件)
- 安装 node.js
- 全局安装grunt (npm install -g grunt-cli)
- 命令行切换目录到ueditor目录下(含有Gruntfile.js文件)
- npm install 安装依赖
- 执行命令grunt default,命令会将源码文件打包,同时会生成 ueditor.all.js 文件,执行完后会在 ueditor 目录下生成一个 dist 目录
- dist里面有个utf8-php目录,里面就是部署版本,修改重命名为ueditor
服务器需要处理的ueditor相关请求
把UEditor部署版本放到项目的static静态目录下,然后按照官网的说明在相关要使用的页面引入ueditor.config.js和ueditor.all.js文件,就可以使用UEditor的绝大部分功能。只有上传文件、图片、视频、在线图片、在线文件这一部分和上传有关系的部分不好用,因为这些功能需要后端服务器的支持才可以,所以需要在我们的django项目中实现响应的视图函数来处理这部分请求:
- 获取后端配置项信息请求(请求地址携带的参数为action=config)
- 上传图片请求(请求地址携带参数看后端配置项设置的具体值,默认为:action=uploadimage)
- 上传视频请求(请求地址携带参数看后端配置项设置的具体值,默认为:action=uploadvideo)
- 上传附件请求(请求地址携带参数看后端配置项设置的具体值,默认为:action=uploadfile)
- 列出所有图片请求(请求地址携带参数看后端配置项设置的具体值,默认为:action=listimage)
- 列出所有附件请求(请求地址携带参数看后端配置项设置的具体值,默认为:action=listfile)
- 上传涂鸦图片请求(请求地址携带参数看后端配置项设置的具体值,默认为:action=uploadscrawl)
- 抓取远程图片(请求地址携带参数看后端配置项设置的具体值,默认为:action=catchimage)
上传图片、上传截图、上传视频、上传附件和上传涂鸦图片,后台服务器应返回的JSON格式数据如下:
{
"state": "SUCCESS", // 状态信息,成功时返回值固定为SUCCESS
"url": "/ueditor/2_%E5%B9%BF%E5%91%8A%E6%8E%92%E6%9C%9F%E5%B9%B3%E5%8F%B0/%E6%9C%80%E6%96%B0%E7%89%A9%E6%96%99%E5%8D%95ID%E6%98%BE%E7%A4%BA%E9%94%99%E8%AF%AF001.png",
"title": "最新物料单ID显示错误001.png", // 文件名称
"original": "最新物料单ID显示错误001.png" // 内部文件名,一般和titile相同
}
抓取远程图片,后台服务器应返回的JSON格式数据如下:
{
"state": "SUCCESS",
"list": [
{
"state": "SUCCESS",
"url": "/ueditor/2_%E5%B9%BF%E5%91%8A%E6%8E%92%E6%9C%9F%E5%B9%B3%E5%8F%B0/sww.png",
"size": 7200,
"title": "sww.png",
"original": "sww.png",
"source": "http://seventest.cn/static/images/sww.png", // 下载地址
}
]
}
列出指定目录下的图片、列出指定目录下的文件,后台服务器应返回的JSON格式数据如下:
{
"state": "SUCCESS",
"list": [
{
"url": "/ueditor/2_%E5%B9%BF%E5%91%8A%E6%8E%92%E6%9C%9F%E5%B9%B3%E5%8F%B0/del_image_01.png"
},
{
"url": "/ueditor/2_%E5%B9%BF%E5%91%8A%E6%8E%92%E6%9C%9F%E5%B9%B3%E5%8F%B0/image.png"
},
{
"url": "/ueditor/2_%E5%B9%BF%E5%91%8A%E6%8E%92%E6%9C%9F%E5%B9%B3%E5%8F%B0/test_picture_002.gif"
},
{
"url": "/ueditor/2_%E5%B9%BF%E5%91%8A%E6%8E%92%E6%9C%9F%E5%B9%B3%E5%8F%B0/%E6%9C%80%E6%96%B0%E7%89%A9%E6%96%99%E5%8D%95ID%E6%98%BE%E7%A4%BA%E9%94%99%E8%AF%AF001.png"
},
{
"url": "/ueditor/2_%E5%B9%BF%E5%91%8A%E6%8E%92%E6%9C%9F%E5%B9%B3%E5%8F%B0/%E6%B6%82%E9%B8%A6.png"
}
],
"start": 0,
"total": 5
}
django项目处理ueditor请求代码
以下是我开发的转测试流程管理系统中实现处理以上ueditor请求的相关代码
-
settings.py配置
-
项目路由配置(urls.py)
# -*- coding:utf-8 -*- ''' Created on 2019年12月15日
@author: siwenwei '''
from django.urls import re_path from . import views
urlpatterns = [
re_path(r'^$', views.Index.as_view()),
re_path(r'^login$', views.Login.as_view()),
re_path(r'^logout$', views.Logout.as_view()),
re_path(r'^userguide$', views.UserGuide.as_view()),
re_path(r'^author$', views.Author.as_view()),
re_path(r'^register$', views.Register.as_view()),
re_path(r'^{}$'.format(views.Settings.suburl), views.Settings.as_view()),
re_path(r'^{}$'.format(views.BaseData.suburl), views.BaseData.as_view()),
re_path(r'^testproject/{}$'.format(views.Project.suburl), views.Project.as_view()),
re_path(r'^user/list$', views.UserList.as_view()),
re_path(r'^member/{}$'.format(views.Member.suburl), views.Member.as_view()),
re_path(r'^flow/list$', views.FlowList.as_view()),
re_path(r'^flow/{}$'.format(views.Flow.suburl), views.Flow.as_view()),
re_path(r'^flow/node/list$', views.FlowNodes.as_view()),
re_path(r'^flow/node/settings$', views.NodeSettings.as_view()),
re_path(r'^flow/node/run$', views.NodeRunner.as_view()),
re_path(r'^flow/node/file/{}$'.format(views.NodeFiles.suburl), views.NodeFiles.as_view()),
re_path(r'^{}$'.format(views.UEditor.SERVER_URL['value']), views.UEditor.as_view()), # 处理 ueditor 获取服务端配置 上传图片 上传附件 列出所有图片 列出所有附件 删除图片 删除附件等请求
re_path(r'^{}/{}$'.format(views.UEditor.SERVER_URL['value'], views.Ueditor_FileServer.suburl), views.Ueditor_FileServer.as_view()),
# 处理ueditor 获取各种文件的请求,其实就是提供获取静态文件的服务 ]
- 处理ueditor各种上传请求、图片列表、附件列表请求视图(ueditor.py)
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
'''
@Author: 思文伟
@Date: 2021/03/07 14:45:44
'''
import os
import json
import base64
from urllib.parse import quote
from urllib.parse import unquote
from urllib.request import urlopen
from django.views import View
from django.conf import settings
from django.http import HttpResponse
from django.http import JsonResponse
from django.http import HttpResponseBadRequest
from django.core.exceptions import ObjectDoesNotExist
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
from django.http import QueryDict
from .. import models
from ..utils.filesize import FileSize
from ..utils.ueditor_path_formatter import UeditorPathFormatter
@method_decorator(csrf_exempt, name='dispatch')
class UEditor(View):
ROOT_DIR = settings.UEDITOR_FILE_DIR
SERVER_URL = {'key': 'serverUrl', 'value': 'ueditor', 'root_dir': ROOT_DIR}
# 上传图片配置项
IMAGE = {
"imageActionName": "uploadimage", # 执行上传图片的action名称
"imageMaxSize": 20485760, # 上传大小限制,单位B,10M
"imageFieldName": "upfile", # * 提交的图片表单名称
"imageUrlPrefix": "",
"imagePathFormat": "",
"imageAllowFiles": [".png", ".jpg", ".jpeg", ".gif", ".bmp"], # 上传图片格式显示
}
# 涂鸦图片上传配置项
SCRAWL = {
"scrawlActionName": "uploadscrawl", # 执行上传涂鸦的action名称
"scrawlFieldName": "upfile", # 提交的图片表单名称
"scrawlMaxSize": 10485760, # 上传大小限制,单位B 10M
"scrawlUrlPrefix": "",
"scrawlPathFormat": "",
}
# 截图工具上传
SNAPSCREEN = {
"snapscreenActionName": "uploadimage", # 执行上传截图的action名称
"snapscreenPathFormat": "",
"snapscreenUrlPrefix": "",
}
# 抓取远程图片配置
CATCHER = {
"catcherLocalDomain": ["127.0.0.1", "localhost", "img.baidu.com"],
"catcherPathFormat": "",
"catcherActionName": "catchimage", # 执行抓取远程图片的action名称
"catcherFieldName": "source", # 提交的图片列表表单名称
"catcherMaxSize": 10485760, # 上传大小限制,单位B
"catcherAllowFiles": [".png", ".jpg", ".jpeg", ".gif", ".bmp"], # 抓取图片格式显示
"catcherUrlPrefix": "",
}
# 上传视频配置
VIDEO = {
"videoActionName": "uploadvideo", # 执行上传视频的action名称
"videoPathFormat": "",
"videoFieldName": "upfile", # 提交的视频表单名称
"videoMaxSize": 102400000, # 上传大小限制,单位B,默认100MB
"videoUrlPrefix": "",
"videoAllowFiles": [".flv", ".swf", ".mkv", ".avi", ".rm", ".rmvb", ".mpeg", ".mpg", ".ogg", ".ogv", ".mov", ".wmv", ".mp4", ".webm", ".mp3", ".wav", ".mid"], # 上传视频格式显示
}
# 上传文件配置
__FILE_ALLOW_FILES = [
".png", ".jpg", ".jpeg", ".gif", ".bmp", ".flv", ".swf", ".mkv", ".avi", ".rm", ".rmvb", ".mpeg", ".mpg", ".ogg", ".ogv", ".mov", ".wmv", ".mp4", ".webm", ".mp3", ".wav", ".mid", ".rar", ".zip", ".tar",
".gz", ".7z", ".bz2", ".cab", ".iso", ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx", ".pdf", ".txt", ".md", ".xml"
]
FILE = {
"fileActionName": "uploadfile", # controller里,执行上传视频的action名称
"filePathFormat": "",
"fileFieldName": "upfile", # 提交的文件表单名称
"fileMaxSize": 204800000, # 上传大小限制,单位B,200MB
"fileUrlPrefix": "", # 文件访问路径前缀
"fileAllowFiles": __FILE_ALLOW_FILES, # 上传文件格式显示
}
# 列出指定目录下的图片
IMAGE_MANAGER = {
"imageManagerActionName": "listimages", # 执行图片管理的action名称
"imageManagerListPath": "",
"imageManagerListSize": 30, # 每次列出文件数量
# 列出的文件类型
"imageManagerAllowFiles": [".png", ".jpg", ".jpeg", ".gif", ".bmp"],
"imageManagerUrlPrefix": "", # 图片访问路径前缀
}
# 列出指定目录下的文件
__FILE_MANAGER_ALLOW_FILES = [
".png", ".jpg", ".jpeg", ".gif", ".bmp", ".tif", ".psd"
".flv", ".swf", ".mkv", ".avi", ".rm", ".rmvb", ".mpeg", ".mpg", ".ogg", ".ogv", ".mov", ".wmv", ".mp4", ".webm", ".mp3", ".wav", ".mid", ".rar", ".zip", ".tar", ".gz", ".7z", ".bz2", ".cab", ".iso",
".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx", ".pdf", ".txt", ".md", ".xml", ".exe", ".com", ".dll", ".msi"
]
FILE_MANAGER = {
"fileManagerActionName": "listfiles", # 执行文件管理的action名称
"fileManagerListPath": "",
"fileManagerUrlPrefix": "",
"fileManagerListSize": 30, # 每次列出文件数量
"fileManagerAllowFiles": __FILE_MANAGER_ALLOW_FILES # 列出的文件类型
}
DEFAULT_MAX_SIZE = 20485760
IMAGE_REMOVE = {"imageRemoveActionName": "removeimage"}
FILE_REMOVE = {"fileRemoveActionName": "removefile"}
SAVE_TO_SERVER = {"saveToServerActionName": "savecontent"}
@property
def upload_config(self):
"""返回给前端的上传配置项"""
self.IMAGE.setdefault('imageActionName ', 'uploadimage')
self.SCRAWL.setdefault("scrawlActionName", "uploadscrawl")
self.SNAPSCREEN.setdefault("snapscreenActionName", "uploadimage")
self.CATCHER.setdefault("catcherActionName", "catchimage")
self.VIDEO.setdefault("videoActionName", "uploadvideo")
self.FILE.setdefault("fileActionName", "uploadfile")
self.IMAGE_MANAGER.setdefault("imageManagerActionName", "listimages")
self.FILE_MANAGER.setdefault("fileManagerActionName", "listfiles")
self.IMAGE_REMOVE.setdefault("imageRemoveActionName", "removeimage")
self.FILE_REMOVE.setdefault("fileRemoveActionName", "removefile")
self.SAVE_TO_SERVER.setdefault("saveToServerActionName", "savecontent")
to_be_add_items = [self.IMAGE, self.SCRAWL, self.SNAPSCREEN, self.CATCHER]
to_be_add_items.append(self.VIDEO)
to_be_add_items.append(self.FILE)
to_be_add_items.append(self.IMAGE_MANAGER)
to_be_add_items.append(self.FILE_MANAGER)
to_be_add_items.append(self.IMAGE_REMOVE)
to_be_add_items.append(self.FILE_REMOVE)
to_be_add_items.append(self.SAVE_TO_SERVER)
items = {}
for item in to_be_add_items:
items.update(item)
return items
@property
def action_views(self):
"""配置对应action的视图函数"""
views = {
'config': self.get_ueditor_config,
self.upload_config.get('imageActionName '): self.general_upload_file,
self.upload_config.get("scrawlActionName"): self.upload_scrawl,
self.upload_config.get("snapscreenActionName"): self.general_upload_file,
self.upload_config.get("catcherActionName"): self.catch_remote_image,
self.upload_config.get("videoActionName"): self.general_upload_file,
self.upload_config.get("fileActionName"): self.general_upload_file,
self.upload_config.get("imageManagerActionName"): self.list_images,
self.upload_config.get("fileManagerActionName"): self.list_files,
self.upload_config.get("imageRemoveActionName"): self.remove_file,
self.upload_config.get("fileRemoveActionName"): self.remove_file,
self.upload_config.get("saveToServerActionName"): self.save_ueditor_content,
}
return views
@staticmethod
def to_json(obj):
return json.dumps(obj, ensure_ascii=False)
@staticmethod
def get_or_post(request):
if request.method.upper() == 'get'.upper():
return request.GET
elif request.method.upper() == 'post'.upper():
parts = request.get_full_path().split('?')
query_params = parts[1] if len(parts) > 1 else ''
qd = QueryDict(query_params)
final_post = request.POST.copy()
final_post.update(qd.copy())
return final_post
else:
return None
def call_action_views(self, request, *args, **kwargs):
params = self.get_or_post(request)
action = params.get('action')
action_view = self.action_views.get(action, None)
if action_view:
return action_view(request, *args, **kwargs)
else:
message = '没有为{}配置对应的视图函数'.format(action)
return HttpResponseBadRequest(message)
def create_full_file_path(self, subpath):
"""构建ueditor上传存放目录
Args:
subpath: 子路径
"""
return os.path.join(self.ROOT_DIR, subpath)
def get_ueditor_config(self, request, *args, **kwargs):
"""返回配置"""
return HttpResponse(self.to_json(self.upload_config), content_type="application/javascript")
def get(self, request, *args, **kwargs):
"""获取ueditor的后端URL地址"""
return self.call_action_views(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
"""获取ueditor的后端URL地址"""
return self.call_action_views(request, *args, **kwargs)
def list_images(self, request, *args, **kwargs):
params = self.get_or_post(request)
allow_types_key = 'imageManagerAllowFiles'
list_size_key = 'imageManagerListSize'
list_path_key = 'imageManagerListPath'
list_size = int(params.get('size', self.upload_config.get(list_size_key, 7)))
list_start = int(params.get("start", 0))
listpath = self.upload_config.get(list_path_key, '')
allow_types = self.upload_config.get(allow_types_key, '')
subdirs = self._project_flow_node_dirname(params.get('project_id', ''), params.get('flow_pk', ''), params.get('node_id', ''))
files = self.get_url_files(listpath, allow_types, listpath_dirs=subdirs)
if (len(files) == 0):
return_info = {"state": "未找到匹配文件!", "list": [], "start": list_start, "total": 0}
else:
return_info = {"state": "SUCCESS", "list": files[list_start:list_start + list_size], "start": list_start, "total": len(files)}
print(json.dumps(return_info))
return HttpResponse(json.dumps(return_info), content_type="application/javascript")
def remove_file(self, request, *args, **kwargs):
state = True
msg = ''
params = self.get_or_post(request)
subdirs = self._project_flow_node_dirname(params.get('project_id', ''), params.get('flow_pk', ''), params.get('node_id', ''))
suburlpath = unquote(params.get('path', ''))
parts = suburlpath.split('/')
root_dir = self.SERVER_URL['root_dir']
if parts:
fpath = subdirs
fpath.append(parts[-1])
filepath = os.path.join(root_dir, *fpath)
try:
if os.path.isfile(filepath):
os.remove(filepath)
except Exception as e:
msg = str(e)
state = False
res = dict(state=state, msg=msg)
dumps_params = dict(ensure_ascii=False)
return JsonResponse(res, json_dumps_params=dumps_params)
def list_files(self, request, *args, **kwargs):
"""列出文件"""
params = self.get_or_post(request)
allow_types_key = 'fileManagerAllowFiles'
list_size_key = 'fileManagerListSize'
list_path_key = 'fileManagerListPath'
list_size = int(params.get('size', self.upload_config.get(list_size_key, 7)))
list_start = int(params.get("start", 0))
listpath = self.upload_config.get(list_path_key, '')
allow_types = self.upload_config.get(allow_types_key, '')
subdirs = self._project_flow_node_dirname(params.get('project_id', ''), params.get('flow_pk', ''), params.get('node_id', ''))
files = self.get_url_files(listpath, allow_types, listpath_dirs=subdirs)
if (len(files) == 0):
return_info = {"state": "未找到匹配文件!", "list": [], "start": list_start, "total": 0}
else:
return_info = {"state": "SUCCESS", "list": files[list_start:list_start + list_size], "start": list_start, "total": len(files)}
return HttpResponse(json.dumps(return_info), content_type="application/javascript")
def get_url_files(self, listpath, allow_types=[], listpath_dirs=[]):
urlsep = '/'
listpath_dirs.extend(listpath.split(urlsep))
dirlist = listpath_dirs
url_files = []
root_dir = self.SERVER_URL['root_dir']
rd_parts = root_dir.split(os.sep)
plength = len(rd_parts)
root_url = self.SERVER_URL['value']
if dirlist:
dirpath = os.path.join(root_dir, os.sep.join(dirlist))
items = os.listdir(dirpath)
for item in items:
if os.path.isfile(os.path.join(dirpath, item)):
name, ext = os.path.splitext(item)
urlparts = dirpath.split(os.sep)
urlparts.append(item)
urlparts = urlparts[plength:]
urlparts.insert(0, root_url)
url = quote(urlsep.join(urlparts))
url = url if url.startswith(urlsep) else (urlsep + url)
if allow_types:
if ext in allow_types:
url_files.append({"url": url})
else:
url_files.append({"url": url})
return url_files
def catch_remote_image(self, request, *args, **kwargs):
"""远程抓图,当catchRemoteImageEnable:true时,如果前端插入图片地址与当前web不在同一个域,则由本函数从远程下载图片到本地
"""
state = "SUCCESS"
path_format_key = 'catcherPathFormat'
params = self.get_or_post(request)
allow_type = list(params.get("catcherAllowFiles", self.upload_config.get("catcherAllowFiles", "")))
# max_size = int(params.get("catcherMaxSize", self.upload_config.get("catcherMaxSize", 0)))
remote_urls = params.getlist(self.upload_config['catcherFieldName'], [])
catcher_infos = []
for remote_url in remote_urls:
# 取得上传的文件的原始名称
remote_file_name = os.path.basename(remote_url)
remote_original_name, remote_original_ext = os.path.splitext(remote_file_name)
# 文件类型检验
if remote_original_ext in allow_type:
path_format = params.get(path_format_key, self.upload_config.get(path_format_key, ''))
formatter = UeditorPathFormatter()
formatter.format(remote_original_name, path_format)
subdirectories = self._project_flow_node_dirname(params.get('project_id', ''), params.get('flow_pk', ''), params.get('node_id', ''))
formatter.set_url_path_prefix(*subdirectories) # 项目 流程 节点 构成的路径
full_file_path = self.create_full_file_path(formatter.save_subpath)
self._create_directory(full_file_path)
# 读取远程图片文件
try:
remote_image = urlopen(remote_url)
# 将抓取到的文件写入文件
try:
f = open(full_file_path, 'wb')
f.write(remote_image.read())
f.close()
state = "SUCCESS"
except Exception as E:
state = u"写入抓取图片文件错误:%s" % E.message
except Exception as E:
state = u"抓取图片错误:%s" % E.message
formatter.set_url_path_prefix(self.SERVER_URL['value']) # 添加根路径
catcher_infos.append({
"state": state,
"url": quote(formatter.url_path),
"size": os.path.getsize(full_file_path),
"title": os.path.basename(full_file_path),
"original": remote_file_name,
"source": remote_url
})
return_info = {"state": "SUCCESS" if len(catcher_infos) > 0 else "ERROR", "list": catcher_infos}
return HttpResponse(json.dumps(return_info, ensure_ascii=False), content_type="application/javascript")
@classmethod
def get_db_object(cls, model_klass, **db_fields):
obj = None
try:
obj = model_klass.objects.get(**db_fields)
except ObjectDoesNotExist:
obj = None
return obj
def _project_flow_node_dirname(self, project_id=None, flow_id=None, node_id=None, sep='_'):
dirname_list = []
if node_id:
node = self.get_db_object(models.Node, id=node_id)
dirname_list.append(sep.join([str(node.flow.project.id), node.flow.project.name]))
dirname_list.append(sep.join([node.flow.number]))
dirname_list.append(sep.join([node.name]))
elif flow_id:
flow = self.get_db_object(models.Flow, id=flow_id)
dirname_list.append(sep.join([str(flow.project.id), flow.project.name]))
dirname_list.append(sep.join([flow.number]))
elif project_id:
project = self.get_db_object(models.Project, id=project_id)
dirname_list.append(sep.join([project_id, project.name]))
else:
pass
return dirname_list
def general_upload_file(self, request, *args, **kwargs):
"""上传文件"""
state = "SUCCESS"
params = self.get_or_post(request)
action = params.get('action')
if action == self.upload_config.get('imageActionName'):
submit_field_key = 'imageFieldName'
max_size_field = 'imageMaxSize'
ext_field = 'imageAllowFiles'
path_format_key = 'imagePathFormat'
elif action == self.upload_config.get('fileActionName'):
submit_field_key = 'fileFieldName'
max_size_field = 'fileMaxSize'
ext_field = 'fileAllowFiles'
path_format_key = 'filePathFormat'
elif action == self.upload_config.get('videoActionName'):
submit_field_key = 'videoFieldName'
max_size_field = 'videoMaxSize'
ext_field = 'videoAllowFiles'
path_format_key = 'videoPathFormat'
else:
return JsonResponse({'state': 'action传值错误'})
submit_field_name = params.get(submit_field_key, self.upload_config.get(submit_field_key, 'upfile'))
submit_file = request.FILES.get(submit_field_name, None)
if submit_file is None:
return JsonResponse({'state': '未上传任何文件'})
submit_file_name = submit_file.name
submit_file_size = submit_file.size
submit_file_ext = os.path.splitext(submit_file_name)[1]
support_file_exts = list(params.get(ext_field, self.upload_config.get(ext_field, "")))
if submit_file_ext not in support_file_exts:
state = "服务器只支持以下类型的文件:{},实际上传的是:{}".format(' | '.join(support_file_exts), submit_file_name)
return JsonResponse({'state': state})
limit_size = int(params.get(max_size_field, self.upload_config.get(max_size_field, self.DEFAULT_MAX_SIZE)))
asize = FileSize(submit_file_size)
lsize = FileSize(limit_size)
if asize > lsize:
state = "上传文件大小({})已超过最大限制({})%s。".format(asize.human_readable, lsize.human_readable)
return JsonResponse({'state': state})
path_format = params.get(path_format_key, self.upload_config.get(path_format_key, ''))
formatter = UeditorPathFormatter()
formatter.format(submit_file_name, path_format)
subdirectories = self._project_flow_node_dirname(params.get('project_id', ''), params.get('flow_pk', ''), params.get('node_id', ''))
formatter.set_url_path_prefix(*subdirectories) # 项目 流程 节点 构成的路径
full_file_path = self.create_full_file_path(formatter.save_subpath)
try:
self._create_directory(full_file_path)
with open(full_file_path, 'wb') as f:
for chunk in submit_file.chunks():
f.write(chunk)
except Exception as e:
state = "保存文件{}错误: {}".format(submit_file_name, str(e))
formatter.set_url_path_prefix(self.SERVER_URL['value']) # 添加根路径
res = dict(state=state, url=quote(formatter.url_path), title=submit_file_name, original=submit_file_name)
dumps_params = dict(ensure_ascii=False)
return JsonResponse(res, json_dumps_params=dumps_params)
def _create_directory(self, path):
target_dir = os.path.dirname(path)
if not os.path.exists(target_dir):
os.makedirs(target_dir)
def upload_scrawl(self, request, *args, **kwargs):
"""上传涂鸦"""
state = "SUCCESS"
sfn = 'scrawlFieldName'
max_size_field = 'scrawlMaxSize'
scrawl_file_name = kwargs.get('scrawl_file_name', '涂鸦.png')
params = self.get_or_post(request)
field_name = params.get(sfn, self.upload_config.get(sfn, 'upfile'))
base64_content = params.get(field_name)
content = base64.b64decode(base64_content)
actual_size = len(content)
limit_size = int(params.get(max_size_field, self.upload_config.get(max_size_field, 0)))
asize = FileSize(actual_size)
lsize = FileSize(limit_size)
if asize > lsize:
state = "上传文件大小({})已超过最大限制({})%s。".format(asize.human_readable, lsize.human_readable)
return JsonResponse({'state': state})
path_format = params.get('scrawlPathFormat', self.upload_config.get('scrawlPathFormat', ''))
formatter = UeditorPathFormatter()
formatter.format(scrawl_file_name, path_format)
subdirectories = self._project_flow_node_dirname(params.get('project_id', ''), params.get('flow_pk', ''), params.get('node_id', ''))
formatter.set_url_path_prefix(*subdirectories) # 项目 流程 节点 构成的路径
full_file_path = self.create_full_file_path(formatter.save_subpath)
try:
self._create_directory(full_file_path)
with open(full_file_path, 'wb') as f:
f.write(content)
except Exception as e:
state = "写入图片文件错误: {}".format(str(e))
formatter.set_url_path_prefix(self.SERVER_URL['value']) # 添加根路径
res = dict(state=state, url=quote(formatter.url_path), title=scrawl_file_name, original=scrawl_file_name)
dumps_params = dict(ensure_ascii=False)
return JsonResponse(res, json_dumps_params=dumps_params)
def save_ueditor_content(self, request, *args, **kwargs):
state = True
msg = ""
submit_datas = self.get_or_post(request)
project_id = submit_datas.get('project_id', '')
flow_id = submit_datas.get('flow_pk', '')
node_id = submit_datas.get('node_id', '')
ueditor_content = submit_datas.get("ueditor_content")
node = self.get_db_object(models.Node, id=node_id)
if node:
if node.is_review_kind or node.is_env_kind or node.is_test_kind:
r = self.get_db_object(models.Result, node=node, batch=node.batch)
if r:
r.doc = ueditor_content
r.save()
else:
code, name = (-1, "还没有结果")
nr = models.Result(code=code, name=name, node=node, batch=node.batch, creator=request.user, doc=ueditor_content)
nr.save()
else:
state = False
msg = "节点({})不支持".format(node.name)
else:
flow = self.get_db_object(models.Flow, id=flow_id)
if flow:
flow.doc = ueditor_content
flow.save()
else:
project = self.get_db_object(models.Project, id=project_id)
if project:
project.doc = ueditor_content
project.save()
else:
state = False
# 项目 流程 节点 任意一个都找不到,不知道保存到哪
msg = "error code: p{}f{}n{}".format(project_id, flow_id, node_id)
res = dict(state=state, msg=msg)
dumps_params = dict(ensure_ascii=False)
return JsonResponse(res, json_dumps_params=dumps_params)
- 提供ueditor获取静态文件的服务视图(ueditor_fileserver.py)
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
'''
@Author: 思文伟
@Date: 2021/03/07 14:47:32
'''
from django.views import View
from django.conf import settings
from django.views import static
from django.http import HttpResponseRedirect
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
@method_decorator(csrf_exempt, name='dispatch')
class Ueditor_FileServer(View):
suburl_group_name = 'suburl'
suburl = '(?P<{}>.+)'.format(suburl_group_name)
redirect_url = "/login"
@property
def ueditor_file_dir(self):
return settings.UEDITOR_FILE_DIR
def download(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return HttpResponseRedirect(self.redirect_url)
sub_url = kwargs.get(self.suburl_group_name)
document_root = self.ueditor_file_dir
path = sub_url
res = static.serve(request, path, document_root=document_root)
return res
def get(self, request, *args, **kwargs):
return self.download(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.download(request, *args, **kwargs)
- 处理文件大小(filesize.py)
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
'''
@Author: 思文伟
@Date: 2021/03/07 14:48:18
'''
class FileSize():
SIZE_UNIT = {"Byte": 1, "KB": 1024, "MB": 1048576, "GB": 1073741824, "TB": 1099511627776}
def __init__(self, size):
self.size = int(FileSize.Format(size))
@staticmethod
def Format(size):
import re
if isinstance(size, int):
return size
else:
if not isinstance(size, str):
return 0
else:
oSize = size.lstrip().upper().replace(" ", "")
pattern = re.compile(r"(\d*\.?(?=\d)\d*)(byte|kb|mb|gb|tb)", re.I)
match = pattern.match(oSize)
if match:
m_size, m_unit = match.groups()
if m_size.find(".") == -1:
m_size = int(m_size)
else:
m_size = float(m_size)
if m_unit != "BYTE":
return m_size * FileSize.SIZE_UNIT[m_unit]
else:
return m_size
else:
return 0
# 返回字节为单位的值
@property
def size(self):
return self._size
@size.setter
def size(self, newsize):
try:
self._size = int(newsize)
except Exception:
self._size = 0
# 返回带单位的自动值
@property
def human_readable(self):
if self.size < FileSize.SIZE_UNIT["KB"]:
unit = "Byte"
elif self.size < FileSize.SIZE_UNIT["MB"]:
unit = "KB"
elif self.size < FileSize.SIZE_UNIT["GB"]:
unit = "MB"
elif self.size < FileSize.SIZE_UNIT["TB"]:
unit = "GB"
else:
unit = "TB"
if (self.size % FileSize.SIZE_UNIT[unit]) == 0:
return "%s%s" % ((self.size / FileSize.SIZE_UNIT[unit]), unit)
else:
return "%0.2f%s" % (round(float(self.size) / float(FileSize.SIZE_UNIT[unit]), 2), unit)
def __str__(self):
return self.human_readable
# 相加
def __add__(self, other):
if isinstance(other, FileSize):
return FileSize(other.size + self.size)
else:
return FileSize(FileSize(other).size + self.size)
def __sub__(self, other):
if isinstance(other, FileSize):
return FileSize(self.size - other.size)
else:
return FileSize(self.size - FileSize(other).size)
def __gt__(self, other):
if isinstance(other, FileSize):
if self.size > other.size:
return True
else:
return False
else:
if self.size > FileSize(other).size:
return True
else:
return False
def __lt__(self, other):
if isinstance(other, FileSize):
if other.size > self.size:
return True
else:
return False
else:
if FileSize(other).size > self.size:
return True
else:
return False
def __ge__(self, other):
if isinstance(other, FileSize):
if self.size >= other.size:
return True
else:
return False
else:
if self.size >= FileSize(other).size:
return True
else:
return False
def __le__(self, other):
if isinstance(other, FileSize):
if other.size >= self.size:
return True
else:
return False
else:
if FileSize(other).size >= self.size:
return True
else:
return False
- 处理ueditor中的PathFormat相关项(ueditor_path_formatter.py)
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
'''
@Author: 思文伟
@Date: 2021/03/07 14:39:03
'''
import re
import os
import random
import datetime
class UeditorPathFormatter(object):
def __init__(self, url_path_sep='/'):
self.url_path_sep = url_path_sep
self.url_path_prefix = None
self.url_path = None
def _replace(self, file_name, rand_prefix='rand:', dtime=datetime.datetime.now()):
def wrapper(match_obj):
ms = match_obj.group(0)
if ms == '{filename}':
repl = file_name
elif ms == '{time}':
repl = str(int(dtime.timestamp()))
elif ms == '{yyyy}':
repl = dtime.strftime('%Y')
elif ms == '{yy}':
repl = dtime.strftime('%y')
elif ms == '{mm}':
repl = dtime.strftime('%m')
elif ms == '{dd}':
repl = dtime.strftime('%d')
elif ms == '{hh}':
repl = dtime.strftime('%H')
elif ms == '{ii}':
repl = dtime.strftime('%M')
elif ms == '{ss}': # datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f') # 含微秒的日期时间
repl = dtime.strftime('%S')
else:
ms_content = ms.lstrip("{").rstrip("}")
if ms_content.startswith(rand_prefix):
length = int(ms_content[len(rand_prefix):])
number = random.randint(1, int('9' * length))
fmt = '{:>0%sd}' % length
repl = fmt.format(number)
else:
repl = ms
return repl
return wrapper
def _check_path_format(self, path_format):
pass
def _check_file_name(self, file_name):
pass
def format(self, file_name, path_format, endswith_filename=True):
"""
# {filename} # 会替换成文件名 [要注意中文文件乱码问题]
# {rand:6} # 会替换成随机数,后面的数字是随机数的位数
# {time} # 会替换成时间戳
# {yyyy} # 会替换成四位年份
# {yy} # 会替换成两位年份
# {mm} # 会替换成两位月份
# {dd} # 会替换成两位日期
# {hh} # 会替换成两位小时
# {ii} # 会替换成两位分钟
# {ss} # 会替换成两位秒
Args:
file_name: 文件名(含拓展名) eg: index.html
path_format: ueditor 相关项的PathFormat 参见 http://fex.baidu.com/ueditor/#server-path
endswith_filename: 控制path_format 不是以文件名结尾的话是否自动附加文件名结尾, True - 附加
"""
self.reset()
parts = [
"\\{filename\\}",
"\\{rand:\\d+\\}",
"\\{time\\}",
"\\{yyyy\\}",
"\\{yy\\}",
"\\{mm\\}",
"\\{dd\\}",
"\\{hh\\}",
"\\{ii\\}",
"\\{ss\\}",
]
file_name_fmt = '{filename}'
regex = '(' + '|'.join(parts) + ')'
matcher = re.compile(regex)
if endswith_filename and not path_format.endswith(file_name_fmt):
path_format = '/'.join([path_format, file_name_fmt])
self.url_path = matcher.sub(self._replace(file_name), path_format)
return self.url_path
@property
def save_subpath(self):
try:
p = self.url_path
except AttributeError:
raise AttributeError('Please call format method before calling save_subpath.')
parts = p.split(self.url_path_sep)
return os.path.join('', *parts)
def set_url_path_prefix(self, *directories):
self.url_path_prefix = self.url_path_sep.join(directories)
if not self.url_path_prefix.startswith(self.url_path_sep):
self.url_path_prefix = self.url_path_sep + self.url_path_prefix
if self.url_path.startswith(self.url_path_sep):
self.url_path = self.url_path_prefix + self.url_path
else:
self.url_path = self.url_path_prefix + self.url_path.lstrip(self.url_path_sep)
def reset(self):
self.url_path_prefix = None
self.url_path = None
if __name__ == '__main__':
formatter = UeditorPathFormatter()
formatter.format('index.html', "/ueditor/php/upload/image/{yyyy}{mm}{dd}/{time}/{rand:11}/{filename}")
print(formatter.url_path)
print(formatter.save_subpath)