天天看点

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

    处理上传文件会更好。

继续阅读