首发于Hello, Flask!
Flask文件上传(二):使用扩展实现

Flask文件上传(二):使用扩展实现

上篇文章

介绍了用Flask原生实现上传功能的过程。阅读这篇文章前,确保你已经读过了上篇文章。为了便于理解,这篇文章的章节安排和上一篇基本相同。

这次介绍使用扩展Flask-Uploads实现上传功能。



Flask文件上传系列目录

  1. Flask文件上传(一):原生实现
  2. Flask文件上传(二):使用扩展实现
  3. Flask文件上传(三):完整实现
  4. Flask文件上传(四):文件管理与多文件上传
  5. Flask文件上传(五):拖拽上传和进度条


Flask-Uploads

Flask-Uploads帮你简化了大部分操作。比如在上篇文章中,我们需要自己设置一个集合来设置允许哪些类型的文件(ALLOWED_EXTENSIONS),而Flask-Uploads已经把常用的文件类型分好类,你只需要导入相应的集合名称,比如IMAGES、TEXT、AUDIO等等(默认配置为DEFAULTS,包括TEXT + DOCUMENTS + IMAGES + DATA)。除此之外,你还可以配置全部允许(All)、除某些文件类型外全允许(AllExcept())。这些集合也可以组合使用(比如IMAGES+TEXT)。



上传配置

因为Flask-Uploads允许你创建不同的set(代表你上传的文件的集合),所以配置时要把下面的FILES替换成你的set名。

比如文件储存地址设置:

UPLOADED_FILES_DEST = '/path/to/the/uploads'

如果你的set叫photos,那么这条设置就改成:

UPLOADED_PHOTOS_DEST = '/path/to/the/uploads'

你也可以设置一个默认的配置:

UPLOADS_DEFAULT_DEST = '/path/to/the/uploads'

更多的可用配置见文档。



安全问题

1、文件类型过滤

创建一个set(通过实例化UploadSet()类实现),然后使用configure_uploads()方法注册并完成相应的配置(类似大多数扩展提供的初始化类),传入当前应用实例和set:

photos = UploadSet('photos', IMAGES)
configure_uploads(app, photos)

这里的photos是set的名字,它很重要。因为接下来它就代表你已经保存的文件,对它调用save()方法保存文件,对它调用url()获取文件url,对它调用path()获取文件的绝对地址……(你可以把它类比成代表数据库的db)


2、文件名过滤

不再需要secure_filename()函数来处理文件名,使用set的save()方法直接保存从request对象获取的文件,将返回处理后的文件名:

filename = photos.save(request.files['photo'])

可能还需要再提醒一下,这里的['photo']是input字段的name值。

3、限制文件大小

导入patch_request_class()函数,传入应用实例和大小(默认为16MB),比如:

patch_request_class(app)

或是

patch_request_class(app, 32 * 1024 * 1024)  # 或是从配置里读取大小

获取文件

你不必再创建新的视图函数来获取文件,Flask-Uploads自带了一个视图函数,当你对一个set(比如我们上面创建的photos)使用url()方法(传入文件名作为参数),比如:

photos.url('demo.jpg')

它会返回一个文件的url:

http://example.com/_uploads/photos/demo.jpg  # /<setname>/<path:filename>


完整实现

同样是一个图片上传的demo,这次要简单的多。

# -*- coding: utf-8 -*-
import os
from flask import Flask, request
from flask_uploads import UploadSet, configure_uploads, IMAGES,\
 patch_request_class

app = Flask(__name__)
app.config['UPLOADED_PHOTOS_DEST'] = os.getcwd()  # 文件储存地址

photos = UploadSet('photos', IMAGES)
configure_uploads(app, photos)
patch_request_class(app)  # 文件大小限制,默认为16MB

html = '''
    <!DOCTYPE html>
    <title>Upload File</title>
    <h1>图片上传</h1>
    <form method=post enctype=multipart/form-data>
         <input type=file name=photo>
         <input type=submit value=上传>
    </form>
    '''


@app.route('/', methods=['GET', 'POST'])
def upload_file():
    if request.method == 'POST' and 'photo' in request.files:
        filename = photos.save(request.files['photo'])
        file_url = photos.url(filename)
        return html + '<br><img src=' + file_url + '>'
    return html


if __name__ == '__main__':
    app.run()

注:视图函数里的两个‘photo’均指的是input字段的name值。

Gist地址:gist.github.com/greyli/




大型项目配置

有同学问到这个,补充一下。在大型项目里,可以像其他扩展一样,在程序工厂函数里初始化,在表单文件里导入set。

__init__.py

# -*-coding: utf-8-*-

from flask import Flask
from flask_bootstrap import Bootstrap
from flask_mail import Mail
from flask_moment import Moment
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager
from config import config
from flask_uploads import UploadSet, configure_uploads, IMAGES  # 导入


bootstrap = Bootstrap()
mail = Mail()
moment = Moment()
db = SQLAlchemy()
photos = UploadSet('photos', IMAGES)  # 创建set


def create_app(config_name):
    app = Flask(__name__)
    app.config.from_object(config[config_name])
    config[config_name].init_app(app)

    bootstrap.init_app(app)
    mail.init_app(app)
    moment.init_app(app)
    db.init_app(app)

    configure_uploads(app, photos)  # 初始化

    return app

在forms.py或views.py中导入set:

from .. import photos

怎么导入视你的目录结构而定,这里的目录结构如下,photos放在__init__.py文件里:

app/ 
   - __init__.py
   - main/
     - forms.py
     - ...


相关链接


- - - - -

更多关于Flask和Web开发的原创内容,欢迎关注知乎专栏 - Hello, Flask!

编辑于 2017-01-06 15:48