天天看點

Python學習筆記:6.3.6 上傳檔案

前言:本文是學習網易微專業的《python全棧工程師 - Flask進階建站》課程的筆記,歡迎學習交流。同時感謝老師們的精彩傳授!

一、課程目标

  • 檔案資料接收
  • 檔案格式與大小限制
  • 檔案名生成規則

二、詳情解讀

2.1.表單設定

前端表單設定:

enctype

:規定在發送到伺服器之前應該如何對表單資料進行編碼。

預設值:

"application/x-www-form-urlencoded"

上傳設定:

"multipart/form-data"

- 表示多種編碼資料。

2.1.1.檔案上傳域

accept

:允許上傳的檔案格式,比如:

"image/gif, image/jpeg"

multiple

:允許一次上傳多張圖檔

Python學習筆記:6.3.6 上傳檔案

實操:

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對象)

Python學習筆記:6.3.6 上傳檔案
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')
           

當上傳的檔案類型不是允許的,會報下面的錯誤:

Python學習筆記:6.3.6 上傳檔案

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

    處理上傳檔案會更好。

繼續閱讀