Flask文件上传(二):使用扩展实现
介绍了用Flask原生实现上传功能的过程。阅读这篇文章前,确保你已经读过了上篇文章。为了便于理解,这篇文章的章节安排和上一篇基本相同。
这次介绍使用扩展Flask-Uploads实现上传功能。
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地址:https://gist.github.com/greyli/addff01c19ddca78cddf386800e57045
大型项目配置
有同学问到这个,补充一下。在大型项目里,可以像其他扩展一样,在程序工厂函数里初始化,在表单文件里导入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-Uploads文档:http://pythonhosted.org/Flask-Uploads/
- Flask-Uploads项目地址:https://github.com/maxcountryman/flask-uploads
- - - - -
更多关于Flask和Web开发的原创内容,欢迎关注知乎专栏 - Hello, Flask!。