使用Django时,通过进度条显示文件上传后的处理进度
这篇文章是关于什么的?
由于尝试在Django中显示进度条遇到了一些困难,我决定记录下我所查找到的内容。
我想实现的目标。
我希望能够选择zip文件并上传,在后台对zip文件内的文件进行处理,并在屏幕上显示处理的进度。
实现的方式
经过调查发现,可能有多种方法,比如使用websocket等,但是本次我们将尝试使用Python的异步处理库Celery和Redis的任务队列来实现异步处理。
西芹
这是一个使用Python编写的异步任务队列/工作程序的分布式任务队列系统。它主要用于高效处理需要在后台运行并持续较长时间的任务或作业。
任务队列
任务队列是一个用来存放耗时处理(在这里是文件处理)任务的地方。队列中的任务会被代理人传递给工作者并执行处理,详细内容稍后会提到。
工人
タスクを実行する役割を持ちます。ブローカーから受け取ったタスクを処理し、結果をブローカーに返します。
红薯
redisはオープンソースのインメモリデータベースで、高性能なデータキャッシュ、キーバリューストア、およびメッセージブローカーとして使用されることが一般的です。ディスクではなくメモリ上にデータを保持するため、高速なデータアクセスが可能であり、多くのリクエストを処理することができます。
ここでは redisを Celeryのメッセージブローカーとして使用します。djangoから処理を受け取り、Djangoと Celery worker との間でtaskの受け渡しや結果の受信を行います。
芹菜进展
使用Celery Progress库,该库专门用于在Django中使用Celery来显示进度条,我们尝试创建一个进度条。
环境设置
由于代码已经在以下网址上公开,因此我将简要解释一下内容。
https://github.com/ekity1002/django-progressbar-sample
以下是使用Python和相关主要库的版本。
-
- python 3.9.5
-
- django 4.2
-
- django-redis 5.2.0
-
- celery 5.2.7
- celery-progress 0.3
创建和配置Django项目。
我正在使用名为django_progressbar的项目来创建项目。由于没有特别不同之处,所以省略了详细信息。
安装必要的库
我正在安装以下的库。
pip install django-redis celery celery-progress
The configuration of Celery.
为了在Django中使用Celery,我在django_progressbar目录下创建了一个名为celery.py的文件,并进行了Celery的配置。(几乎与下面的官方文档中提供的配置相同。)
https://docs.celeryq.dev/en/stable/django/first-steps-with-django.html
以下是不同之处,即将Celery的broker配置为使用redis的部分。
import os
from celery import Celery
# Set the default Django settings module for the 'celery' program.
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_progressbar.settings")
# Celery app作成
# project名, brokerのホストを指定
app = Celery("django_progressbar", broker="redis://redis:6379/0")
app.conf.result_backend = "redis://redis:6379/0"
我还在settings.py中添加了以下内容。
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
...
# Celery apps
"celery", #追加
"celery_progress", #追加
]
App设置
观看
我正在创建一个名为upload_file的Django应用程序,并创建一个视图来上传zip文件。
- views.py
import time
import zipfile
from io import BytesIO
from celery import shared_task
from celery_progress.backend import ProgressRecorder
from django.core.files.storage import default_storage
from django.shortcuts import render
from .forms import UploadZipFileForm
def get_file_list(zip_file):
# zipファイル内の全てのファイルを取得する
buffer = BytesIO(zip_file.read())
with zipfile.ZipFile(buffer, "r") as zip_ref:
files = zip_ref.namelist()
return files
@shared_task(bind=True)
def process_files(self, file_list):
print(type(self))
progress_recorder = ProgressRecorder(self)
# 進行状況の初期化
total_files = len(file_list)
progress_recorder.set_progress(0, total_files)
print("total files: ", total_files)
# ファイルを読み込んで、内容を取得する
result = 0
for idx, file in enumerate(file_list):
# 重い処理
print(f"Processing {file}")
time.sleep(0.4)
# 進行状況を更新
print(f"Done!")
result += 1
progress_recorder.set_progress(idx + 1, total_files, description=f"処理中...({idx+1}/{total_files})")
# return "File upload success!"
def upload_zip_file(request):
if request.method == "POST":
form = UploadZipFileForm(request.POST, request.FILES)
if form.is_valid():
# クライアントから送信されたzipファイルを取得する
print("zip uploaded.")
zip_file = request.FILES["zip_file"]
file_list = get_file_list(zip_file)
# ファイルを処理する
result = process_files.delay(file_list)
# zip削除
zip_file.close()
default_storage.delete(zip_file.name)
print(type(result), result)
# 処理が完了したら、リダイレクトなど適切なレスポンスを返す
return render(request, "upload_file/upload.html", context={"form": form, "task_id": result.task_id})
else:
form = UploadZipFileForm()
# GETリクエストの場合は、ファイルアップロードのフォームを表示する
return render(request, "upload_file/upload.html", {"form": form})
所有的流程是,将zip文件上传 -> 在zip文件中,在带有shared_task修饰器的process_files函数中执行的处理由Celery的工作进程异步执行。
在调用process_file时,加上delay()使其变为异步处理。
此外,在该函数内部创建了一个 ProgressRecorder 对象,并记录了用于在 celery-progress 中显示进度条的进度。
模板
除了在upload.html中进行文件上传设置外,还添加了以下描述来实现使用celery-progress显示进度条。
<div class='progress-wrapper'>
<div id='progress-bar' class='progress-bar' style="background-color: #68a9ef; width: 0%;"> </div>
</div>
<div id="progress-bar-message">Waiting for progress to start...</div>
<div id="celery-result">
</div>
{% if task_id %}
<script type="text/javascript">
function processProgress(progressBarElement, progressBarMessageElement, progress) {
console.log(`@@@@@ ${progress.percent} processProgress @@@@@`)
console.log(progress)
progressBarElement.style.width = progress.percent + "%";
var description = progress.description || "アップロード中...";
progressBarMessageElement.innerHTML = description;
}
// Progress Bar (JQuery)
$(function () {
var progressUrl = "{% url 'celery_progress:task_status' task_id %}";
CeleryProgressBar.initProgressBar(progressUrl, {
onProgress: processProgress,
})
});
</script>
{% endif %}
我們幾乎直接使用了 celery-progress 文檔中的配置。
当上传zip文件时,后端会异步执行process_file函数并返回task_id作为响应。然后开始显示进度条。
Docker的配置设置
使用docker创建并运行redis和django的容器。在docker-compose中进行如下设置:
version: '3'
services:
web:
container_name: web
build:
context: .
dockerfile: Dockerfile
ports:
- "8000:8000"
volumes:
- ./app:/app/app
command: bash -c "poetry run celery -A django_progressbar worker -l INFO & poetry run python manage.py runserver 0:8000"
redis:
image: "redis:latest"
container_name: redis
ports:
- "6379:6379"
volumes:
- "./data/redis:/data"
在启动Django容器时,请执行以下两个命令作为注意事项。
poetry run celery -A django_progressbar worker -l INFO #celery worker 起動
poetry run python manage.py runserver 0:8000 #djangoサーバー起動
我们首先执行了Celery命令来启动配置的worker进程,然后启动了Django服务器。
打开
运行docker-compose up -d命令,并访问http://localhost:8000/upload/来上传zip文件,结果如下所示。
动画.gif
另外,celery-progress 还提供了多种自定义外观和行为的选项,如果您有兴趣,请查阅官方文档了解详情。
总结
大致上说,我在Django中记录了在文件上传时显示进度条的方法。
虽然一开始看起来很简单,但比预想中困难,因此对我来说是一次学习机会。
关于与其他实现方法,比如websocket等的优缺点的比较,我还不太了解,所以也想调查一下这方面的信息。