90行Python代码开发个人云盘应用

2021-05-15 0 311

本文示例代码已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes

1 简介

在今天的教程中,我们将介绍如何在Dash中高效地开发web应用中非常重要的「文件上传」及「下载」功能。

2 在Dash中实现文件上传与下载

2.1 在Dash中配合dash-uploader实现文件上传

其实在自带的dash_core_components中就封装了基于html5原生API的dcc.Upload()组件,可以实现简单的文件上传功能,但说实话,非常的「不好用」,其主要缺点有:

  • 「文件大小有限制,150M到200M左右即出现瓶颈」
  • 「策略是先将用户上传的文件存放在浏览器内存,再通过base64形式传递到服务端再次解码,非常低效」
  • 「整个上传过程无法配合准确的进度条」

正是因为Dash自带的上传部件如此不堪,所以一些优秀的第三方拓展涌现出来,其中最好用的要数dash-uploader,它解决了上面提到的dcc.Upload()的所有短板。通过pip install dash-uploader进行安装之后,就可以直接在Dash应用中使用了。

我们先从极简的一个例子出发,看一看在Dash中使用dash-uploader的正确姿势:

app1.py

import dash
import dash_uploader as du
import dash_bootstrap_components as dbc
import dash_html_components as html

app = dash.Dash(__name__)

# 配置上传文件夹
du.configure_upload(app, folder=\'temp\')

app.layout = html.Div(
    dbc.Container(
        du.Upload()
    )
)

if __name__ == \'__main__\':
    app.run_server(debug=True)

90行Python代码开发个人云盘应用

可以看到,仅仅十几行代码,我们就配合dash-uploader实现了简单的文件上传功能,其中涉及到dash-uploader两个必不可少的部分:

2.1.1 利用du.configure_upload()进行配置

要在Dash中正常使用dash-uploader,我们首先需要利用du.configure_upload()进行相关配置,其主要参数有:

「app」,即对应已经实例化的Dash对象;

「folder」,用于设置上传的文件所保存的根目录,可以是相对路径,也可以是绝对路径;

「use_upload_id」,bool型,默认为True,这时被用户上传的文件不会直接置于「folder」参数指定目录,而是会存放于du.Upload()部件的upload_id对应的子文件夹之下;设置为False时则会直接存放在根目录,当然没有特殊需求还是不要设置为False。

通过du.configure_upload()我们就完成了基本的配置。

2.1.2 利用du.Upload()创建上传部件

接下来我们就可以使用到du.Upload()来创建在浏览器中渲染供用户使用的上传部件了,它跟常规的Dash部件一样具有「id」参数,也有一些其他的丰富的参数供开发者充分自由地自定义功能和样式:

「text」,字符型,用于设置上传部件内显示的文字;

「text_completed」,字符型,用于设置上传完成后显示的文字内容前缀;

「cancel_button」,bool型,用于设置是否在上传过程中显示“取消”按钮;

「pause_button」,bool型,用于设置是否在上传过程中显示“暂停”按钮;

「filetypes」,用于限制用户上传文件的格式范围,譬如[\’zip\’, \’rar\’, \’7zp\’]就限制用户只能上传这三种格式的文件。默认为None即无限制;

「max_file_size」,int型,单位MB,用于限制单次上传的大小上限,默认为1024即1GB;

「default_style」,类似常规Dash部件的style参数,用于传入css键值对,对部件的样式进行自定义;

「upload_id」,用于设置部件的唯一id信息作为du.configure_upload()中所设置的缓存根目录的下级子目录,用于存放上传的文件,默认为None,会在Dash应用启动时自动生成一个随机值;

「max_files」,int型,用于设置一次上传最多可包含的文件数量,默认为1,也推荐设置为1,因为目前对于多文件上传仍有「进度条异常」、「上传结束显示异常」等bug,所以不推荐设置大于1。

知晓了这些参数的作用之后,我们就可以创建出更符合自己需求的上传部件:

app2.py

import dash
import dash_uploader as du
import dash_bootstrap_components as dbc
import dash_html_components as html

app = dash.Dash(__name__)

# 配置上传文件夹
du.configure_upload(app, folder=\'temp\')

app.layout = html.Div(
    dbc.Container(
        du.Upload(
            id=\'uploader\',
            text=\'点击或拖动文件到此进行上传!\',
            text_completed=\'已完成上传文件:\',
            cancel_button=True,
            pause_button=True,
            filetypes=[\'md\', \'mp4\'],
            default_style={
                \'background-color\': \'#fafafa\',
                \'font-weight\': \'bold\'
            },
            upload_id=\'我的上传\'
        )
    )
)

if __name__ == \'__main__\':
    app.run_server(debug=True)

90行Python代码开发个人云盘应用

但像前面的例子那样直接在定义app.layout时就传入实际的du.Upload()部件,会产生一个问题——应用启动后,任何访问应用的用户都对应一样的upload_id,这显然不是我们期望的,因为不同用户的上传文件会混在一起。

因此可以参考下面例子的方式,在每位用户访问时再渲染随机id的上传部件,从而确保唯一性:

app3.py

import dash
import dash_uploader as du
import dash_bootstrap_components as dbc
import dash_html_components as html

import uuid

app = dash.Dash(__name__)

# 配置上传文件夹
du.configure_upload(app, folder=\'temp\')

def render_random_id_uploader():

    return du.Upload(
            id=\'uploader\',
            text=\'点击或拖动文件到此进行上传!\',
            text_completed=\'已完成上传文件:\',
            cancel_button=True,
            pause_button=True,
            filetypes=[\'md\', \'mp4\'],
            default_style={
                \'background-color\': \'#fafafa\',
                \'font-weight\': \'bold\'
            },
            upload_id=uuid.uuid1()
        )

def render_layout():

    return html.Div(
    dbc.Container(
        render_random_id_uploader()
    )
)

app.layout = render_layout

if __name__ == \'__main__\':
    app.run_server(debug=True)

可以看到,每次访问时由于upload_id不同,因此不同的会话拥有了不同的子目录。

90行Python代码开发个人云盘应用

2.1.3 配合du.Upload()进行回调

在du.Upload()中额外还有isCompleted与fileNames两个属性,前者用于判断当前文件是否上传完成,后者则对应此次上传的文件名称,参考下面这个简单的例子:

app4.py

import dash
import dash_uploader as du
import dash_bootstrap_components as dbc
import dash_html_components as html
from dash.dependencies import Input, Output, State

app = dash.Dash(__name__)

# 配置上传文件夹
du.configure_upload(app, folder=\'temp\')

app.layout = html.Div(
    dbc.Container(
        [
            du.Upload(id=\'uploader\'),
            html.H5(\'上传中或还未上传文件!\', id=\'upload_status\')
        ]
    )
)


@app.callback(
    Output(\'upload_status\', \'children\'),
    Input(\'uploader\', \'isCompleted\'),
    State(\'uploader\', \'fileNames\')
)
def show_upload_status(isCompleted, fileNames):
    if isCompleted:
        return \'已完成上传:\'+fileNames[0]

    return dash.no_update


if __name__ == \'__main__\':
    app.run_server(debug=True, port=8051)

90行Python代码开发个人云盘应用

2.2 配合flask进行文件下载

相较于文件上传,在Dash中进行文件的下载就简单得多,因为我们可以配合flask的send_from_directory以及html.A()部件来为指定的服务器端文件创建下载链接,譬如下面的简单示例就打通了文件的上传与下载:

app5.py

from flask import send_from_directory
import dash
import dash_uploader as du
import dash_html_components as html
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output
import os

app = dash.Dash(__name__)

du.configure_upload(app, \'temp\', use_upload_id=False)

app.layout = html.Div(
    dbc.Container(
        [
            du.Upload(id=\'upload\'),
            html.Div(
                id=\'download-files\'
            )
        ]
    )
)

@app.server.route(\'/download/<file>\')
def download(file):

    return send_from_directory(\'temp\', file)

@app.callback(
    Output(\'download-files\', \'children\'),
    Input(\'upload\', \'isCompleted\')
)
def render_download_url(isCompleted):

    if isCompleted:
        return html.Ul(
            [
                html.Li(html.A(f\'/{file}\', href=f\'/download/{file}\', target=\'_blank\'))
                for file in os.listdir(\'temp\')
            ]
        )

    return dash.no_update

if __name__ == \'__main__\':
    app.run_server(debug=True)

90行Python代码开发个人云盘应用

3 用Dash编写简易个人网盘应用

在学习了今天的案例之后,我们就掌握了如何在Dash中开发文件上传及下载功能,下面我们按照惯例,结合今天的主要内容,来编写一个实际的案例;

今天我们要编写的是一个简单的个人网盘应用,我们可以通过浏览器访问它,进行文件的上传、下载以及删除:

90行Python代码开发个人云盘应用

import dash
import dash_bootstrap_components as dbc
import dash_html_components as html
from dash.dependencies import Input, Output, State
import dash_uploader as du
import os
from flask import send_from_directory
import time

app = dash.Dash(__name__, suppress_callback_exceptions=True)

du.configure_upload(app, \'NetDisk\', use_upload_id=False)

app.layout = html.Div(
    dbc.Container(
        [
            html.H3(\'简易的个人云盘应用\'),
            html.Hr(),
            html.P(\'文件上传区:\'),
            du.Upload(id=\'upload\',
                      text=\'点击或拖动文件到此进行上传!\',
                      text_completed=\'已完成上传文件:\',
                      max_files=1000),
            html.Hr(),
            dbc.Row(
                [
                    dbc.Button(\'删除选中的文件\', id=\'delete-btn\', outline=True),
                    dbc.Button(\'打包下载选中的文件\', id=\'download-btn\', outline=True)
                ]
            ),
            html.Hr(),
            dbc.Spinner(
                dbc.Checklist(
                    id=\'file-list-check\'
                )
            ),
            html.A(id=\'download-url\', target=\'_blank\')
        ]
    )
)


@app.server.route(\'/download/<file>\')
def download(file):
    return send_from_directory(\'NetDisk\', file)


@app.callback(
    [Output(\'file-list-check\', \'options\'),
     Output(\'download-url\', \'children\'),
     Output(\'download-url\', \'href\')],
    [Input(\'upload\', \'isCompleted\'),
     Input(\'delete-btn\', \'n_clicks\'),
     Input(\'download-btn\', \'n_clicks\')],
    State(\'file-list-check\', \'value\')
)
def render_file_list(isCompleted, delete_n_clicks, download_n_clicks, check_value):
    # 获取上下文信息
    ctx = dash.callback_context

    if ctx.triggered[0][\'prop_id\'] == \'delete-btn.n_clicks\':

        for file in check_value:
            try:
                os.remove(os.path.join(\'NetDisk\', file))
            except FileNotFoundError:
                pass

    if ctx.triggered[0][\'prop_id\'] == \'download-btn.n_clicks\':

        import zipfile

        with zipfile.ZipFile(\'NetDisk/打包下载.zip\', \'w\') as zipobj:
            for file in check_value:
                try:
                    zipobj.write(os.path.join(\'NetDisk\', file))
                except FileNotFoundError:
                    pass

        return [
                   {\'label\': file, \'value\': file}
                   for file in os.listdir(\'NetDisk\')
                   if file != \'打包下载.zip\'
               ], \'打包下载链接\', \'/download/打包下载.zip\'

    time.sleep(2)

    return [
               {\'label\': file, \'value\': file}
               for file in os.listdir(\'NetDisk\')
               if file != \'打包下载.zip\'
           ], \'\', \'\'


if __name__ == \'__main__\':
    app.run_server(debug=True)

以上就是90行Python代码开发个人云盘应用的详细内容,更多关于python 开发个人云盘的资料请关注自学编程网其它相关文章!

遇见资源网 Python 90行Python代码开发个人云盘应用 http://www.ox520.com/28564.html

常见问题

相关文章

发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务