Flask-Dropzone:为你的Flask程序添加文件上传功能

这篇文章属于“Flask常用扩展介绍系列”,这个系列的文章目录索引可以在《Flask常用扩展介绍系列文章索引》看到。
 

某天在Stack Overflow上看到一个关于Dropzone.js的问题,就去研究了一下,写了一个回答,顺便写了一个小demo。我发现,如果有一个集成Dropzone.js到Flask,并且简化设置步骤的扩展,肯定要比其他上传方式简单的多——于是就有了Flask-Dropzone

Flask-Dropzone在模板中提供了一些方法来帮助你创建上传区域,引入相关资源。你只需要添加一些配置就可以实现上传类型的过滤,文件大小限制,上传后跳转等功能。当然,你还要自己编写视图函数来处理和保存文件,并进行服务器端的二次验证。

《Flask Web开发实战》中的第3个示例程序(图片社交程序Albumy)使用了这个扩展。

用法说明

安装

$ pip install flask-dropzone

初始化

和其他扩展类似,你可以通过实例化Dropzone类,并传入程序实例app进行初始化:

from flask_dropzone import Dropzone

app = Flask(__name__)
dropzone = Dropzone(app)

在使用工厂函数创建程序实例时,你也可以使用init_app()方法:

from flask_dropzone import Dropzone

dropzone = Dropzone()

def create_app():
    app = Flask(__name__)
    dropzone.init_app(app)
    return app

引入Dropzone.js资源

你需要自己手动编写引入Dropzone.js的CSS和JavaScript资源的语句。在开发时,或对于玩具项目,你可以可以使用Flask-Dropzone提供的两个快捷方法:

<head>
    ...
    {{ dropzone.load_css() }}
</head>
<body>
    ...
    {{ dropzone.load_js() }}
</body>

创建并美化上传区域

如果你不需要对上传区域的样式有太多控制,那么你只需要在想要渲染上传区域的地方使用dropzone.create()方法:

{{ dropzone.create(action='处理上传文件的路由URL') }}

记得把action的值更改成你要处理文件上传的的URL。你可以使用style()方法为上传区域添加简单的自定义样式:

<head>
...  <!-- 在引入Dropzone.js的CSS文件后调用style()方法 -->
{{ dropzone.style('border: 2px dashed #0087F7; margin: 10%') }}
</head>

上传区域的截图示例如下所示:

Flask-Dropzone上传截图

Flask-Dropzone上传截图

在服务器端处理并保存上传文件

当文件被拖拽到上传区域,或是点击上传区域选择上传文件后,这些文件会以AJAX请求的形式发送到你在dropzone.create()方法中使用action参数传入的URL。我们需要在服务器端创建对应的视图函数来处理这些请求,下面是一个最基本的示例:

import os

from flask import Flask, request
from flask_dropzone import Dropzone

app = Flask(__name__)

dropzone = Dropzone(app)

@app.route('/uploads', methods=['GET', 'POST'])
def upload():

    if request.method == 'POST':  # 如果请求类型为POST,说明是文件上传请求
        f = request.files.get('file')  # 获取文件对象
        f.save(os.path.join('the/path/to/save', f.filename))  # 保存文件

    return 'upload template'  # 渲染上传页面

 

上传完成后重定向

这里需要注意的是,因为Dropzone.js通过AJAX请求提交文件,所以你没法在保存文件后将页面重定向。对于这个问题,你可以使用配置变量DROPZONE_REDIRECT_VIEW设置上传完成后跳转到的目标端点,或是添加一个按钮让用户自己点击进行跳转。

服务器端验证

尽管Dropzone.js可以在前端对用户提交的文件进行验证,但为了安全考虑,我们仍然需要在服务器端进行二次验证。在服务器端验证时,如果验证出错,我们不能像往常那样使用flash()函数“闪现”错误消息,因为AJAX请求接受到响应后并不会重载页面,所以不会显示通过flash()函数发送的消息。正确的做法是返回400错误响应,使用错误消息作为响应的主体。下面是一个简单的进行服务器端验证并返回错误消息得示例:

@app.route('/', methods=['POST', 'GET'])
def upload():
    if request.method == 'POST':
        f = request.files.get('file')
        if f.filename.split('.')[1] != 'png':
            return 'PNG only!', 400  # return the error message, with a proper 4XX code
        f.save(os.path.join('the/path/to/save', f.filename))
    return render_template('index.html')

在上面的代码中,我们验证图片是不是png格式,如果不是就返回一个错误提示,在服务器端会在图片下面看到我们返回的错误消息:

 

 

完整的配置列表

Flask-Dropzone提供了丰富的配置变量,你可以使用它们对Dropzone.js进行各类配置。

Name Default Value Info
DROPZONE_SERVE_LOCAL   False       使用Flask-Dropzone提供的load_css()和load_js()方法时,这个变量设置是否使用内置的本地资源,默认从CND加载
DROPZONE_MAX_FILE_SIZE 3 允许的文件最大值,单位为MB
DROPZONE_INPUT_NAME file 上传字段的name值
DROPZONE_ALLOWED_FILE_CUSTOM False 是否使用自定义文件类型允许规则,具体见后面
DROPZONE_ALLOWED_FILE_TYPE 'default' 允许的文件类型,具体见后面
DROPZONE_MAX_FILES ‘null’ 一次可以上传的文件数量最大值
DROPZONE_DEFAULT_MESSAGE “Drop files here to upload” 上传区域显示的提示文字
DROPZONE_INVALID_FILE_TYPE “You can’t upload files of this type.” 文件类型错误的错误消息
DROPZONE_FILE_TOO_BIG “File is too big {{filesize}}. Max filesize: {{maxFilesize}}MiB.” 文件太大时显示的错误消息
DROPZONE_SERVER_ERROR “Server error: {{statusCode}}” 服务器错误的错误消息
DROPZONE_BROWSER_UNSUPPORTED “Your browser does not support drag’n’drop file uploads.” 浏览器不支持的错误消息
DROPZONE_MAX_FILE_EXCEED “Your can’t upload any more files.” 超过最大文件数量限制的错误消息
DROPZONE_UPLOAD_MULTIPLE False 是否在单个请求中发送多个文件,默认一个请求发送一个文件
DROPZONE_PARALLEL_UPLOADS 2 当DROPZONE_UPLOAD_MULTIPLE设为True时,设置单个请求包含的文件数量
DROPZONE_REDIRECT_VIEW None 上传完成后重定向的模板端点
DROPZONE_ENABLE_CSRF False 是否开启CSRF保护

这些配置的用法你可以参考Flask-Drozone的文档或是示例程序了解,这里我们仅简单介绍一下对文件类型进行过滤的设置方法。

设置文件类型过滤

Flask-Dropzone内置了一些文件类型(通过MIME定义),可选的值和对应的文件类型如下所示:

  • default:默认值,运行所有类型
  • image:图片
  • audio:音频
  • video:视频
  • text:文本
  • app:程序

你需要为DROPZONE_ALLOWED_FILE_TYPE设置对应的值,比如下面设置仅允许上传图片:

app.config['DROPZONE_ALLOWED_FILE_TYPE'] = 'image'

如果你想要自己定义允许的文件类型列表,那么你需要将DROPZONE_ALLOWED_FILE_CUSTOM设置True,然后传入一个包含允许的文件后缀名列表组成的字符串给DROPZONE_ALLOWED_FILE_TYPE变量,使用逗号分隔多个后缀名,比如:

app.config['DROPZONE_ALLOWED_FILE_CUSTOM'] = True
app.config['DROPZONE_ALLOWED_FILE_TYPE'] = 'image/*, .pdf, .txt'

对于平行上传、CSRF保护等内容的具体实现方法你可以参考文档了解。不过,这个项目目前还没有创建完善的文档,暂时只是写到README里,如果你发现了英文语法或拼写错误,欢迎指正,同时也欢迎为项目贡献代码。

示例程序

Flask-Dropzone的Git仓库中的examples目录下包含4个示例程序,分别演示了基本用法、CSRF保护、平行上传和上传完成后跳转四个功能。

源码可以在这里找到:https://github.com/greyli/flask-dropzone/tree/master/examples

另外,helloflask仓库里在demos/form目录下的示例程序也包含一个Flask-Dropzone使用示例。

相关链接

 

Flask-Dropzone:为你的Flask程序添加文件上传功能》上有10条评论

  1. 头像yaoxiezhen

    李老师,您好:

    我们需要做一个in_form的dropzone,但是在html里的,加上蓝图前缀的时候,就会报错:werkzeug.routing.BuildError: Could not build url for endpoint ”. 不加蓝图前缀,就add_course,又会提示是否应该是’cam_pus.add_course’

    回复
  2. 头像莫莫

    李老师,
    我想用dropzone单独做一个上传的应用。
    项目里,如果表单有上传需求,就先到固定上传应用服务地址上传。上传成功返回文件路径给应用。这样不用在项目里去集成上传的功能。维护模块化。
    该如何实现?

    回复
  3. 头像guomaoqiu

    请问一下李老师,{{ dropzone.create(action=’处理上传文件的路由URL’) }} ,如果这个路由中需要携带一个参数该怎么处理呢?
    比如我的上传路由是这样的:
    @main.route(‘/add-photo/’, methods=[‘GET’, ‘POST’])
    ,这个id在html中已经可以拿到的。

    回复
      1. 头像guomaoqiu

        还是报错:
        BuildError: Could not build url for endpoint ‘add_photo’ with values [‘id’]. Did you mean ‘main.add_photo’ instead?

        html 中我是这样写的:
        {{ dropzone.create(action=url_for(‘add_photo’, id=album.id )) }}

        views 中:
        @main.route(‘/add-photo/’, methods=[‘GET’, ‘POST’])

        回复

撰写评论

电子邮件地址不会被公开,必填项已用 * 标出。