事情是这样的。
有一天五号程序员打算网购一盒巧克力送给自己的女朋友
想必女朋友收到礼物是这样的:
结果商家邮寄来的巧克力中夹着一张纸条:
害!你说气不气人,现在都还没理我!
这不投诉更待何时?
打开投诉界面一看,咦?要求上传图片。
哈~既然说起上传,那咱们就先说一下如何使用Flask上传文件。
一、使用Flask上传文件的简单实现
Flask文件上传比较简单,主要是下面3点:
l 一个<form>标签被标记有enctype = multipart/form-data ,并且在里面包含一个<input type=file>标签
l 服务端应用通过请求对象上的files字典访问文件
l 使用文件的save()方法将文件永久的保存在文件系统的某处
既然涉及到保存,那肯定会用到路径。下面是os.path方法的相关属性:
l os.path.sep:Windows下路径分隔符,即反斜杠 ‘’;
l os.path.altsep:Linux下路径分隔符,即斜杠‘/‘;
l 当前目录:os.path.curdir;
l 父目录:os.path.pardir;
l 绝对路径:os.path.abspath(path)
有了这些基本知识就够了,接下来直接实战。新建app.py文件,内容如下:
from flask import Flask,render_template,request
from os import path
from werkzeug.utils import secure_filename
app = Flask(__name__)
@app.route('/', methods=['GET', 'POST'])
def index():
if request.method == 'GET':
return render_template('upload.html')
else:
f = request.files['file']
filename = secure_filename(f.filename)
f.save(path.join('D:/test', filename))
#文件的具体路径根据个人而定,这里以D盘test文件夹为例
return "上传文件成功!"
if __name__ == "__main__":
app.run(debug=True)
导入secure_filename方法是为了在传入中文文件名时过滤掉中文名,只留下文件名后缀;path.join()则可以整合路径。
在templates文件夹下新建upload.html文件,用于文件上传:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>文件上传</title>
<style type="text/css">
.div1 {
height:180px;
width:380px;
border:1px solid #8A8989;
margin:0 auto;
}
.input {
display: block;
width:250px;
height:30px;
margin:10px auto;
}
.button {
background: #2066C5;
color:white;
font-size:18px;
font-weight: bold;
height: 30px;
border-radius: 4px;
}
</style>
</head>
<body>
<div class="div1">
<form action="" method="post" enctype="multipart/form-data">
<input type="file" name="file" class="input">
<input type="submit" value="上传" class="input button">
</form>
</div>
</body>
</html>
运行文件后将能看见如下界面:
选择好指定的word文件:
点击上传,显示“上传成功”:
由于在form标签中action属性没有指定其他网页,所以会跳转到同一个网址,但此时的request.method已经由GET变为POST,所以执行else下的代码。
看一下文件夹内有没有:
成功!我们的电脑就像一个服务器,可以实现任意文件的上传,但是以中文命名的文件上传时会出现中文丢失。
二、改进上传功能
虽然上面的代码实现了文件上传功能,但还有一些不足:
l 文件没有重新命名,多名用户上传同名文件如何处理?
l 没有实现文件目录自动创建,文件保存失败怎么办?
l 文件上传时没有进行文件格式检验,影响数据安全怎么设卡?
为了解决上述问题,新建form.py文件用于表单验证:
from wtforms import Form,FileField
from flask_wtf.file import FileRequired,FileAllowed
class UploadForm(Form):
file = FileField(validators=[FileRequired(), FileAllowed(['jpg','png','gif'])])
其中,FileRequired()用来验证文件上传是否为空,FileAllowed()用来验证上传文件的后缀名。
然后修改app.py文件:
from flask import Flask,render_template,request,send_from_directory
import time
import os
from os import path
from werkzeug.utils import secure_filename
from werkzeug.datastructures import CombinedMultiDict
import platform
from form import UploadForm
app = Flask(__name__)
#判断系统,使用不同分隔符
if platform.system() == "Windows":
slash = ''
else:
platform.system() == "Linux"
slash = '/'
UPLOAD_PATH = 'D:' + slash + 'uploads' + slash
@app.route('/', methods=['GET', 'POST'])
def index():
if request.method == 'GET':
return render_template('upload.html')
else:
if not path.exists(UPLOAD_PATH):
os.makedirs(UPLOAD_PATH) #路径不存在时创建路径
form = UploadForm(CombinedMultiDict([request.form, request.files]))
if form.validate():
f = request.files['file']
filename = secure_filename(f.filename)
ext = filename.rsplit('.', 1)[1] #获取文件后缀
unix_time = int(time.time()) #获取时间用于命名,不会重复
new_filename = str(unix_time) + '.' + ext
f.save(path.join(UPLOAD_PATH, new_filename))
return "上传文件成功!"
else:
return "仅支持jpg、png和gif格式的文件!"
@app.route('/images/<filename>/', methods=['GET', 'POST'])
def get_image(filename):
return send_from_directory(UPLOAD_PATH, filename)
if __name__ == "__main__":
app.run(debug=True)
其中,os.makedirs()可以自动创建文件路径,然后用CombinedMultiDict()把form和file的数据结合起来一起验证,用时间戳加文件后缀重新命名文件,最后新建get_image()视图函数用于浏览或下载文件,并用send_from_directory(dirpath, filename)函数将文件进行在线下载功能。
运行app.py,选择在桌面上的“handsome.jpg”文件:
点击上传可以看到“文件上传成功!”。找一下电脑中的源文件:
可以看到在D盘下生成了uploads文件夹,接下来在浏览器访问:
http://127.0.0.1:5000/images/xxx.jpg/,根据保存时的文件名输入网址实现在线下载功能。
如果上传word文档行不行呢?试一下:
不行!报错!
因为上传的文件只能是三种图片格式!
三
看到这里有些小伙伴可能会感到熟悉,是不是在哪里见过?
没错,百度贴吧!
登录百度美图吧,随机找到一张图片并打开:
观察其网址,再点开同一帖子下的图片观察其网址,你会发现其中的命名规则。
今天的内容图比较多,看起来比较费劲,上传文件的内容非常重要,需要多加练习才能掌握!
下一次,我们将谈一谈经常提到的Cookies。