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


Posted in Python onApril 20, 2021

本文示例代码已上传至我的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 相关文章推荐
python解决字典中的值是列表问题的方法
Mar 04 Python
Python脚本实现下载合并SAE日志
Feb 10 Python
python中input()与raw_input()的区别分析
Feb 27 Python
Python实现并行抓取整站40万条房价数据(可更换抓取城市)
Dec 14 Python
Python入门_浅谈逻辑判断与运算符
May 16 Python
django如何实现视图重定向
Jul 24 Python
使用Python实现图像标记点的坐标输出功能
Aug 14 Python
Python 共享变量加锁、释放详解
Aug 28 Python
python 瀑布线指标编写实例
Jun 03 Python
你需要学会的8个Python列表技巧
Jun 24 Python
在Python中字典按值排序的实现方法
Nov 12 Python
python画条形图的具体代码
Apr 20 Python
基于python实现银行管理系统
python爬虫框架feapde的使用简介
Apr 20 #Python
python实现大文本文件分割成多个小文件
Apr 20 #Python
Python绘制分类图的方法
Pytest allure 命令行参数的使用
在pyCharm中下载第三方库的方法
Python控制台输出俄罗斯方块移动和旋转功能
Apr 18 #Python
You might like
php获取域名的google收录示例
2014/03/24 PHP
php实现的替换敏感字符串类实例
2014/09/22 PHP
Windows下编译PHP5.4和xdebug全记录
2015/04/03 PHP
PHP指定截取字符串中的中英文或数字字符的实例分享
2016/03/18 PHP
PHP读取文件的常见几种方法
2016/11/03 PHP
Mac下php 5升级到php 7的步骤详解
2017/04/26 PHP
PHP Post获取不到非表单数据的问题解决办法
2018/02/27 PHP
PHP与Perl之间知识点区别整理
2019/03/19 PHP
javascript 操作Word和Excel的实现代码
2009/10/26 Javascript
JavaScript与Image加载事件(onload)、加载状态(complete)
2011/02/14 Javascript
jtable列中自定义button示例代码
2013/11/21 Javascript
js抽奖实现随机抽奖代码效果
2013/12/02 Javascript
JavaScript资源预加载组件和滑屏组件的使用推荐
2016/03/10 Javascript
jQuery模拟完美实现经典FLASH导航动画效果【附demo源码下载】
2016/11/09 Javascript
AngularJS模糊查询功能实现代码(过滤内容下拉菜单排序过滤敏感字符验证判断后添加表格信息)
2017/10/24 Javascript
js 图片转base64的方式(两种)
2018/04/24 Javascript
JavaScript 判断iPhone X Series机型的方法
2019/01/28 Javascript
详解将微信小程序接口Promise化并使用async函数
2019/08/05 Javascript
Javascript实现秒表计时游戏
2020/05/27 Javascript
Vue $emit()不能触发父组件方法的原因及解决
2020/07/28 Javascript
[01:23:59]2018DOTA2亚洲邀请赛 4.1 小组赛 B组 VP vs Secret
2018/04/03 DOTA
Python的高级Git库 Gittle
2014/09/22 Python
python使用tensorflow保存、加载和使用模型的方法
2018/01/31 Python
Python pip替换为阿里源的方法步骤
2019/07/02 Python
python 使用装饰器并记录log的示例代码
2019/07/12 Python
Python经纬度坐标转换为距离及角度的实现
2020/11/01 Python
Python 实现图片转字符画的示例(静态图片,gif皆可)
2020/11/05 Python
总结python 三种常见的内存泄漏场景
2020/11/20 Python
python实现银行账户系统
2021/02/22 Python
韩国女装NO.1网店:STYLENANDA
2016/09/16 全球购物
2014年端午节演讲稿范文
2014/05/23 职场文书
小学生植树节活动总结
2014/07/04 职场文书
毕业实习证明(4篇)
2014/10/28 职场文书
导游词之宿迁乾隆行宫
2019/10/15 职场文书
MYSQL(电话号码,身份证)数据脱敏的实现
2021/05/28 MySQL
MySQL时区造成时差问题
2022/04/13 MySQL