前言:本文是学习网易微专业的《python全栈工程师 - Flask高级建站》课程的笔记,欢迎学习交流。同时感谢老师们的精彩传授!
一、课程目标
- 文件数据接收
- 文件格式与大小限制
- 文件名生成规则
二、详情解读
2.1.表单设置
前端表单设置:
enctype
:规定在发送到服务器之前应该如何对表单数据进行编码。
默认值:
"application/x-www-form-urlencoded"
。
上传设定:
"multipart/form-data"
- 表示多种编码数据。
2.1.1.文件上传域
accept
:允许上传的文件格式,比如:
"image/gif, image/jpeg"
multiple
:允许一次上传多张图片
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIn5GcuEjM3EDOzITM1EjMwAjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
实操:
Step1
:创建文件
templates/upload/form_upload.html
,写入以下代码:
{% extends 'base.html' %}
{% block content %}
<form action="" method="post" enctype="multipart/form-data">
<div class="form-group">
<label for="picname">图片名称</label>
<input type="text" name="picname" class="form-control">
</div>
<div class="form-group">
<input type="file" name="file" class="form-control" accept="image/gif">
<input type="submit" name="submit" value="上传" class="btn btn-primary">
</div>
</form>
{% endblock %}
Step2
:创建文件
views/upload.py
,写入以下代码:
# -*- coding=utf-8 -*-
from flask import Blueprint, request, render_template
upload_app = Blueprint('upload', __name__)
#上传文件
@upload_app.route('/', methods=['GET', 'POST'])
def upload():
if request.method == 'POST':
pass
return render_template('upload/form_upload.html')
Step3
:将
Step2
中的
upload_app
注册到
app.py
文件中
.
.
.
from views.users import user_app
from views.articles import article_app
from views.upload import upload_app # 新增这一行
from flask_migrate import Migrate
.
.
.
app.register_blueprint(user_app, url_prefix="/user")
app.register_blueprint(article_app, url_prefix="/article")
app.register_blueprint(upload_app, url_prefix="/upload") # 新增这一行
.
.
.
2.2.后台数据接收
2.2.1.request.files对象
通过
form
表单上传的文件数据,在
flask
内可以通过
request.files
对象获取到:
单个文件:
返回的
file_storage
是
FileStorage
对象实例。
FileStorage
对象实例就是表单上传的文件
实例属性与方法 | 说明 |
---|---|
content_type | 文件类型 |
filename | 上传的文件名 |
name | 文件域名 |
save(file_path) | 将上传的文件保存到file_path |
实操1: 上传单个文件
替换
views/upload.py
内容为以下代码,同时新建目录
static/uploads
:
#上传文件
@upload_app.route('/', methods=['GET', 'POST'])
def upload():
if request.method == 'POST':
file_storage = request.files.get('file')
# 上传类型
print(file_storage.content_type)
# 上传文件原名
print(file_storage.filename)
# 文件域名
print(file_storage.name)
# 使用原名直接保存
file_storage.save("./static/uploads/" + file_storage.filename)
return render_template('upload/form_upload.html')
实操2: 上传多个文件
Step1
:修改文件
templates/upload/form_upload.html
:
.
.
.
<div class="form-group">
<!-- 添加 multiple="multiple" 属性 -->
<input type="file" name="file" class="form-control" accept="image/gif" multiple="multiple">
<input type="submit" name="submit" value="上传" class="btn btn-primary">
</div>
.
.
.
Step2
:替换views/upload.py内容为以下代码:
#上传文件
@upload_app.route('/', methods=['GET', 'POST'])
def upload():
if request.method == 'POST':
# 多文件需要用getlist(),而不是get()
file_storage_list = request.files.getlist('file')
print(file_storage_list)
# 用循环依次保存上传的文件
for file_storage in file_storage_list:
# 上传类型
print(file_storage.content_type)
# 上传文件原名
print(file_storage.filename)
# 文件域名
print(file_storage.name)
# 使用原名直接保存
file_storage.save("./static/uploads/" + file_storage.filename)
return render_template('upload/form_upload.html')
运行结果:
(打印出上传文件的列表,里面都是FileStorage对象)
2.3.安全问题
2.3.1.上传文件是网站安全的一个大问题
网站提供文件上传,必须控制安全性问题:
1.文件格式限制
2.文件大小限制
3.文件存放路径
实操1: 限制文件上传类型
Step1
:在
app.py
中添加配置:
.
.
.
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
app.config['SQLALCHEMY_DATABASE_URI'] = "sqlite:///my.db"
# 新增允许上传文件类型
app.config['ALLOW_UPLOAD_TYPE'] = ["image/gif", "image/png"]
.
.
.
Step2
:替换
views/upload.py
文件内容:
# -*- coding=utf-8 -*-
from flask import Blueprint, request, render_template
from flask import current_app # ++++ 新增这一行 +++++
upload_app = Blueprint('upload', __name__)
#上传文件
@upload_app.route('/', methods=['GET', 'POST'])
def upload():
if request.method == 'POST':
file_storage_list = request.files.getlist('file')
print(file_storage_list)
# file_storage = request.files.get('file')
for file_storage in file_storage_list:
# 上传类型
print(file_storage.content_type)
# ++++++ 新增下面这两行 ++++++
if file_storage.content_type not in current_app.config['ALLOW_UPLOAD_TYPE']:
return "", 403
# 上传文件原名
print(file_storage.filename)
# 文件域名
print(file_storage.name)
# 使用原名直接保存
file_storage.save("./static/uploads/" + file_storage.filename)
return render_template('upload/form_upload.html')
当上传的文件类型不是允许的,会报下面的错误:
实操2: 限制文件上传大小
替换
views/upload.py
文件内容:
# -*- coding=utf-8 -*-
from flask import Blueprint, request, render_template
from flask import current_app
upload_app = Blueprint('upload', __name__)
#上传文件
@upload_app.route('/', methods=['GET', 'POST'])
def upload():
if request.method == 'POST':
file_storage_list = request.files.getlist('file')
print(file_storage_list)
# file_storage = request.files.get('file')
for file_storage in file_storage_list:
# 上传类型
print(file_storage.content_type)
# 获得上传数据长度,以字节为单位, 这里是300k
# 这里是限制所有文件总共大小
# ++++++ 新增下面这两行 ++++++
if request.content_length > 300 *1000:
return "", 403
if file_storage.content_type not in current_app.config['ALLOW_UPLOAD_TYPE']:
return "", 403
# 上传文件原名
print(file_storage.filename)
# 文件域名
print(file_storage.name)
# 使用原名直接保存
file_storage.save("./static/uploads/" + file_storage.filename)
return render_template('upload/form_upload.html')
实操3: 生成文件存放路径
替换
views/upload.py
文件内容为以下代码:
# -*- coding=utf-8 -*-
from flask import Blueprint, request, render_template
from flask import current_app
import os # 新增这一行
upload_app = Blueprint('upload', __name__)
#上传文件
@upload_app.route('/', methods=['GET', 'POST'])
def upload():
if request.method == 'POST':
file_storage_list = request.files.getlist('file')
print(file_storage_list)
# file_storage = request.files.get('file')
for file_storage in file_storage_list:
# 上传类型
print(file_storage.content_type)
# 获得上传数据长度,以字节为单位, 这里是300k
if request.content_length > 300 * 1000 * 1000:
return "", 403
if file_storage.content_type not in current_app.config['ALLOW_UPLOAD_TYPE']:
return "", 403
# 上传文件原名
print(file_storage.filename)
# 文件域名
print(file_storage.name)
# 生成按日期存放的文件路径 新增这一行
file_path = os.path.join(get_dir(), create_filename(file_storage.filename))
print(file_path)
file_storage.save(file_path)
return render_template('upload/form_upload.html')
# 新增这个函数
def get_dir():
'''
生成文件存放路径
:return: 存放文件路径
'''
from datetime import date
# 上传文件存放路径
base_path = "./static/uploads/"
# 根据上传的日期存放
d = date.today()
# 生成存储路径
path = os.path.join(base_path, str(d.year), str(d.month))
if not os.path.exists(path):
# 如果存放路径不存在,尝试创建
os.makedirs(path)
# except Exception as e:
# 如果按照老师的写法,path路径存在的话,os.makedirs()会报错,
# 就会执行下面的语句,这样就在不到按日期存放了。
# # path = base_path
# print(e)
return path
# 新增这个函数
def create_filename(filename):
'''
生成随机文件名
:param filename: 文件原名
:return: 文件名
'''
import uuid
ext = os.path.splitext(filename)[1]
new_file_name = str(uuid.uuid4()) + ext
return new_file_name
2.4.ajax上传体验更好
2.4.1.原生xhr对象上传
xhr.upload
对象 - 用于在数据传输到服务器时收集一些传输信息
xhr.upload.onloadstart = function(ev){
message.innerHTML = '开始上传‘
}
xhr.upload.onprogress = function(ev) {
progress.value = ev.loaded / ev.total * 100
}
其他事件:
事件 | 说明 |
---|---|
onabort | 终止上传 |
onerror | 发生错误 |
onload | 上传数据成功 |
ontimeout | 上传超时 |
onloadend | 上传数据结束(可能成功或者失败) |
实操:
Step1
:新建文件
templates/upload/xhr_upload.html
,写入以下代码:
{% extends 'base.html' %}
{% block content %}
<progress max="100" value="0" id="progress"></progress>
<div id="message"></div>
<div id="image"></div>
<div class="form-group">
<label for="picname">图片名称:</label>
<input type="text" name="picname" class="form-control">
</div>
<div class="form-group">
<input type="file" id="file" name="file" class="form-control" multiple="multiple">
<input type="button" value="上传" class="btn btn-primary">
</div>
<script>
progress = document.getElementById('progress')
message = document.getElementById('message')
image = document.getElementById('image')
btn = document.querySelector('.btn')
xhr = new XMLHttpRequest()
xhr.upload.onloadstart = function(ev) {
message.innerHTML = '开始上传
}
xhr.upload.onprogress = function(ev) {
progress.value = ev.loaded / ev.total * 100
}
xhr.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
data = JSON.parse(this.responseText)
// 上传成功返回一个上传文件存放路径列表
if (data.result == 'success') {
message.innerHTML = '上传成功'
for ( i in data.filepath_list) {
img = document.createElement('img')
img.src = data.filepath_list[i]
image.appendChild(img)
}
} else {
message.innerHTML = '上传失败: ' + data.error
}
}
}
btn.onclick = function() {
// 第一张图片
// document.getElementById('file').files[0]
// 第二张图片
// document.getElementById('file').files[1]
files = document.getElementById('file').files
data = new FormData()
for (i in files) {
data.append('file', files[i])
}
xhr.open('post', '/upload/')
xhr.send(data)
}
</script>
{% endblock %}
Step2
:修改
views/upload.py
文件中的
upload()
视图函数为以下代码:
.
.
.
import json # 文件顶部引入 json 模块
.
.
.
#上传文件
@upload_app.route('/', methods=['GET', 'POST'])
def upload():
if request.method == 'POST':
file_storage_list = request.files.getlist('file')
# 新增message字典,用来返回前端的数据
message = {"result": "", "error": "", "filepath_list": []}
print(file_storage_list)
# file_storage = request.files.get('file')
for file_storage in file_storage_list:
# 上传类型
# print(file_storage.content_type)
# 获得上传数据长度,以字节为单位, 这里是300k
if request.content_length > 300 * 1000:
message['result'] = 'fail'
message['error'] = '上传文件太大'
return json.dumps(message)
if file_storage.content_type not in current_app.config['ALLOW_UPLOAD_TYPE']:
message['result'] = 'fail'
message['error'] = '上传文件类型不对'
return json.dumps(message)
file_path = os.path.join(get_dir(), create_filename(file_storage.filename))
try:
file_storage.save(file_path)
except Exception as e:
message = {"result": "fail", "error": str(e)}
return json.dumps(message)
# [1:]将.static/相对路径转为/static绝对路径
message['filepath_list'].append(file_path[1:])
message['result'] = 'success'
return json.dumps(message)
return render_template('upload/xhr_upload.html')
.
.
.
2.4.2.jQuery的ajax对象
1.不对数据进行编码处理:
默认
“application/x-www-form-urlencoded”
,
false
不设置任何类型。
2.上传图片设为
false
:
3.为
xhr
设置
upload
方法:
xhr: function() {
xhr = $.ajaxSettings.xhr()
...
return xhr
}
实操:
Step1
:新建文件
templates/upload/jquery_upload.html
,并写入以下代码:
{% extends 'base.html' %}
{% block content %}
<progress max="100" value="0" id="progress"></progress>
<div id="message"></div>
<div id="image"></div>
<div class="form-group">
<label for="picname">图片名称:</label>
<input type="text" name="picname" class="form-control">
</div>
<div class="form-group">
<input type="file" id="file" name="file" class="form-control" multiple="multiple">
<input type="button" value="上传" class="btn btn-primary">
</div>
<script src="/static/js/jquery-3.4.1.min.js"></script>
<script>
progress = $('#progress')
message = $('#message')
image = $('#image')
btn = $('.btn')
btn.on('click', function() {
files = document.getElementById('file').files
data = new FormData()
for (i in files) {
data.append('file', files[i])
}
$.ajax({
url: '/upload/',
type: 'post',
data: data,
processData: false,
contentType: false,
dataType: 'json',
success: function(data) {
if (data.result == 'success') {
message.html('上传成功')
console.log(data.filepath_list)
for (i in data.filepath_list) {
img = $('<img />')
img.attr('src', data.filepath_list[i])
image.append(img)
}
} else {
message.html('上传失败:' + data.errors)
}
},
// 设置进度显示
xhr: function() {
xhr = $.ajaxSettings.xhr()
xhr.upload.onloadstart = function() {
message.html('开始上传...')
}
xhr.upload.onprogress = function(e) {
status = e.loaded / e.total * 100
progress.val(status)
}
return xhr
}
})
})
</script>
{% endblock %}
Step2
:
views/upload.py
文件中的
upload()
视图函数,不用做太大的修改,只要将渲染模版改为
jquery_upload.html
即可:
.
.
.
return render_template('upload/jquery_upload.html')
.
.
.
三、课程小结
-
表单上传
通过表单上传,我们可以掌握,当我们需要上传一个文件的时候,前后台应该怎么处理。在后台部分,通过
对象来获取前端传来的数据。request.files
-
对象request.files
-
ajax
上传
通过
上传,可以改善用户的体验,可以提供用户的上传信息反馈,并且可以在上传大文件时显示进度条ajax
-
的jQuery
ajax
上传处理
为了改善
的兼容性,使用ajax
的jQuery
处理上传文件会更好。ajax