前言:本文是學習網易微專業的《python全棧工程師 - Flask進階建站》課程的筆記,歡迎學習交流。同時感謝老師們的精彩傳授!
一、課程目标
- 檔案資料接收
- 檔案格式與大小限制
- 檔案名生成規則
二、詳情解讀
2.1.表單設定
前端表單設定:
enctype
:規定在發送到伺服器之前應該如何對表單資料進行編碼。
預設值:
"application/x-www-form-urlencoded"
。
上傳設定:
"multipart/form-data"
- 表示多種編碼資料。
2.1.1.檔案上傳域
accept
:允許上傳的檔案格式,比如:
"image/gif, image/jpeg"
multiple
:允許一次上傳多張圖檔

實操:
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