大师网-带你快速走向大师之路 解决你在学习过程中的疑惑,带你快速进入大师之门。节省时间,提升效率

基于Editor.MD的Flask图片实现

Editor.MD的Flask图片上传实战

继上篇 基于Flask的Markdown编辑器实践选择的Editor.MD为博客提供的MarkDown编辑器自带图片上传接口,如果不使用Flask-Uploads的话也是很简便的。这篇相当于上篇的一个补充和拓展。

在html里添加这三行:imageUploadURL里填后面用的上传路由

<script type="text/javascript">
    $(function () {
        editormd("fancy-editormd", {
            // ...
            imageUpload : true,
            imageFormats : [ "jpg", "jpeg", "gif", "png", "bmp", "webp" ],
            imageUploadURL : "{{ url_for('.upload') }}",
        });
    });
</script>
112218_7.png
  • 同域上传

如果要同域上传,可以这样写,在path字段改为相应的图片上传目录即可:

@admin_bp.route('/upload/',methods=['POST'])
@login_required
def upload():
    file=request.files.get('editormd-image-file')
    if not file:
        res={
            'success':0,
            'message':'上传失败'
        }
    else:
        ex=os.path.splitext(file.filename)[1]
        filename=datetime.now().strftime('%Y%m%d%H%M%S')+ex
        file.save(filename)
        res={
            'success':1,
            'message':'上传成功',
            'url':url_for('.image',name=filename)
        }
    return jsonify(res)

@admin_bp.route('/image/<name>')
@csrf.exempt
def image(name):
    with open(os.path.join('../uploads',name),'rb') as f:
        resp=Response(f.read(),mimetype="image/jpeg")
    return resp

  • 跨域上传

如果跨域上传,国内图床可以选则常用的七牛云或者阿里云,都大同小异。
这里我以七牛云为例, 七牛云有提供Python-SDK,还是很便利的,另外Github有Flask-QiniuStorage——七牛云存储Flask扩展,Qiniu Storage for Flask
使用教程简单明了,首先pip安装:(利用pipenv)

pipenv install Flask-QiniuStorage

工厂函数中将其实例化:

from flask_qiniustorage import Qiniu
# ...
qiniu_store = Qiniu()
# ...

from cryptic.extensions import qiniu_store
# ...
def register_extensions(app):
qiniu_store.init_app(app)
# ...

类组织配置,Access key 和 Secret key比较敏感,我们选择从环境变量中读取,对应设置即可:

QINIU_ACCESS_KEY = os.getenv('ACCESS_KEY')
QINIU_SECRET_KEY = os.getenv('QINIU_KEY')
QQINIU_BUCKET_NAME = '七牛空间名称'
QINIU_BUCKET_DOMAIN = '七牛空间对应域名'

这边七牛云的后台设置告一段落,我们回头看下Editor.MD
可知文件接收的参数为editormd-image-file
前端需要回调一个固定格式,用于告知前端状态信息与导入的URL地址,如果调用失败无需返回url。

res.json({
    success : 1, 
    message : "这里随便",
    url: imageSrc
})

此时我们差不多就可以编辑上传路由了:
设置文件接收的参数,方法为POSTonly

admin.py:

import os
from datetime import datetime
from flask import jsonify, request
from flask_login import login_required
from xxxxx.extensions import qiniu_store
# ...
@admin_bp.route('/upload/',methods=['POST'])
@login_required
def upload():
data=request.files['editormd-image-file']
if not data:
res={
    'success':0,
    'message':'upload failed'
}
else:
ex=os.path.splitext(data.filename)[1]
    filename=datetime.now().strftime('%Y%m%d%H%M%S')+ex
    qiniu_store.save(file, filename)
    res={
        'success':1,
        'message':'upload success',
        'url':qiniu_store.url(filename)
    }
return jsonify(res)

我们尝试使用request获取文件,遇到了新问题,我们收获了一个400错误

如果前面定义了CSRF错误响应捕捉。此时我们就收到了一个CSRFError

112218_5.png

由于Flask-WTF的CSRF保护开启,然而Editor.md 的上传表单中并没有包含csrftoken。
要么就都添加csrf验证,要么就都关闭
你可以阅览Flask-WTF的文档(http://www.pythondoc.com/flask-wtf/csrf.html)

  • 我们可以通过修改请求文件editormd/plugins/image-dialog/image-dialog.js来添加csrfToken来解决:

image-dialog.js:

if (settings.crossDomainUpload)
{
    action += "&callback=" + settings.uploadCallbackURL + "&dialog_id=editormd-image-dialog-" + guid;
}
var csrfToken = $('meta[name="_token"]').attr('content');
var csrfField = "";
if (csrfToken) {
   csrfField = "<input type='hidden' name='_token' value='" + csrfToken + "' />";
}

修改响应字段,添加csrfField 变量,修改dialogContent为:

var dialogContent = ( (settings.imageUpload) ? "<form action=\"" + action +"\" target=\"" + iframeName + "\" method=\"post\" enctype=\"multipart/form-data\" class=\"" + classPrefix + "form\">" : "<div class=\"" + classPrefix + "form\">" ) +  
                    ( (settings.imageUpload) ? "<iframe name=\"" + iframeName + "\" id=\"" + iframeName + "\" guid=\"" + guid + "\"></iframe>" : "" ) +  
                    "<label>" + imageLang.url + "</label>" +  
                    "<input type=\"text\" data-url />" + (function(){  
                        return (settings.imageUpload) ? "<div class=\"" + classPrefix + "file-input\">" +  
                                                            "<input type=\"file\" name=\"" + classPrefix + "image-file\" accept=\"image/*\" />" +  
                                                            csrfField +  
                                                            "<input type=\"submit\" value=\"" + imageLang.uploadButton + "\" />" +  
                                                        "</div>" : "";  
                    })() +  
                    "<br/>" +  
                    "<label>" + imageLang.alt + "</label>" +  
                    "<input type=\"text\" value=\"" + selection + "\" data-alt />" +  
                    "<br/>" +  
                    "<label>" + imageLang.link + "</label>" +  
                    "<input type=\"text\" value=\"http://\" data-link />" +  
                    "<br/>" + csrfField +  
                ( (settings.imageUpload) ? "</form>" : "</div>");  

这里排版有点乱,只有单独起行的的两个csrfField +,接下来在post表单做相应修改即可。

  • 不过既然我选择了惰性加载CsrfProtect,我暂时可以先直接通过添加@csrf_exempt在view里排除。
    于是最终上传代码如下:

admin.py:

    
    import os, base64
    from datetime import datetime
    from flask import jsonify, request
    from flask_login import login_required
    from xxxxx.extensions import csrf, qiniu_store
    # ...
    @admin_bp.route('/upload/',methods=['POST'])
    @login_required
    @csrf_exempt
    def upload():
    data=request.files['editormd-image-file']
    if not data:
    res={
        'success':0,
        'message':'图片失败请重试'
    }
    else:
    ex=os.path.splitext(data.filename)[1]
        filename=datetime.now().strftime('%Y%m%d%H%M%S')+ex
        file = data.stream.read()
        file = base64.b64encode(file)
        # data.save(filename)
        qiniu_store.save(file, filename)
        res={
            'success':1,
            'message':'图片上传成功',
            'url':qiniu_store.url(filename)
        }
    return jsonify(res)

jsonify自动添加文件头,成功回调的效果如下。

112218_4.png

至此应该都没什么问题了,如果需要显示emoji或者代码高亮这些直接在js脚本里添加相应字段即可,添加必要的css和js文件。