90行Python代码开发个人云盘应用
作者:费弗里 发布时间:2021-12-17 13:44:12
目录
1 简介
2 在Dash中实现文件上传与下载
2.1 在Dash中配合dash-uploader实现文件上传
2.1.1 利用du.configure_upload()进行配置
2.1.2 利用du.Upload()创建上传部件
2.1.3 配合du.Upload()进行回调
2.2 配合flask进行文件下载
3 用Dash编写简易个人网盘应用
本文示例代码已上传至我的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)
可以看到,仅仅十几行代码,我们就配合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)
但像前面的例子那样直接在定义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不同,因此不同的会话拥有了不同的子目录。
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)
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)
3 用Dash编写简易个人网盘应用
在学习了今天的案例之后,我们就掌握了如何在Dash中开发文件上传及下载功能,下面我们按照惯例,结合今天的主要内容,来编写一个实际的案例;
今天我们要编写的是一个简单的个人网盘应用,我们可以通过浏览器访问它,进行文件的上传、下载以及删除:
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)
来源:https://mp.weixin.qq.com/s/wYs7MvtM6gBzSMpprc_NbQ
猜你喜欢
- 在使用操作XML文件时,我们可以使用Load方法直接加载xml文件即可,在ie和ff下通用。但是是XML字符串,则在两种浏览器下就会有所不同
- 在页面中自定义了changejs函数后页面提示错误:Active Server Pages 错误 'ASP 0138' 嵌套
- 在浏览几个网页时,发现有几个网站在显示时间时存在问题,比如今天是2009年1月4日,但是有的网站上却赫然写着今天是109年1月4日(这里有个
- 1、slice结构体首先我们来看一段代码package mainimport ( "fmt"
- 武器档案名称:firebug最新版本:1.7用途:前端调试器必备指数:使用难度:firebug是前端最具盛名的调试器,功能非常强悍。fire
- 在你的程序初始化时使用如下代码: <?php $Php2Html_FileUrl = $_SERVER["REQU
- 实现了宽度、高度、透明度的渐变,还能以高度宽度中点为中心,还扩展成以任意点为中心渐变(实例中以点击点为中心)。<!DOCTYPE ht
- 存储过程采用的是select top 加 not in的方式完成,速度也算是相当快了 我测试过了百万级数据量一般查询在1秒一下,贴出来大家交
- 去掉html中的table代码 Function OutTable(str) dim a,re&nb
- 是什么能让一个设计看上去是协调的,有条理的,专业的?答案是”色彩”.不是所有的项目都要用那种浅的”公司蓝”(corporate blue)才
- 昨天用ucweb看到了goos发的一篇帖子:谁说Float菜单不可以水平居中,进去看了看,觉得方法有点繁琐了,用到了负边距,position
- 由传智播客教程整理,我们这里使用的是python2.7.x版本,就是2.7之后的版本,因为python3的改动略大,我们这里不用它。现在我们
- 在DBA的日常工作中,经常需要重装或在新机器上安装Oracle,但每次安装所浪费的时间、精力以及失败的挫折都在考验着DBA的承受能力,本文着
- 原由定期更换密码是一种非常重要的安全措施,这种做法可以有效地保护你的账户和个人信息不受黑客和网络攻击者的侵害。密码泄露是一个非常普遍的问题,
- 最近一直忙,我们的注册页面还是在持续优化。今天抽时间分析了下数据,依然以主注册表单为例,对表单里3个区块、9个字段做了个小小出错排行;看看哪
- 方法一:在php中,抓取https的网站,提示如下的错误内容:Warning: file_get_contents() [function.
- 数据库是数据的集合,与数学的集合论有密不可分的关系。为提高查询速度,我们可以:对数据表添加索引,以加快搜索速度;通过编程技巧最大限度地利用索
- yaml简单介绍YAML是一种标记语言,它使用空白符号缩进和大量依赖外观的特色,特别适合用来表达或编辑数据结构、各种配置文件、倾印调试内容、
- 本文实例讲述了Django框架基础模板标签与filter使用方法。分享给大家供大家参考,具体如下:一、基本的模板语言1、变量{{ }}1.1
- 下面是代码,如果看不懂,建议先把表格的一些<tr><td>的表格原理弄清楚了,就可以了代码如下:<table&