Skip to content
返回

如何用Django,Celery,AlpineJS还有HTMX SSE实现ProgressBar

Table of contents

Open Table of contents

开发环境

基本的环境可以参考如何在Django中配置Alpine.js,HTMX和tailwindcss

唯二的区别有htmx插件sse,以及celery,请参考以下步骤进行安装配置。

安装配置SSE & Celery

配置SSE

目前安装htmx的插件有两种方式:CDN 以及 npm包管理器。

如果是想快速安装,建议可以使用CDN。把下面链接直接添加到标签中即可:

<head>
    ...
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/htmx.min.js" integrity="sha384-H5SrcfygHmAuTDZphMHqBJLc3FhssKjG7w/CeCpFReSfwBWDTKpkzPP8c+cLsK+V" crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]" integrity="sha384-A986SAtodyH8eg8x8irJnYUk7i9inVQqYigD6qZ9evobksGNIXfeFvDwLSHcp31N" crossorigin="anonymous"></script>
    ...
</head>

如果习惯用npm管理器,也可以执行下面命令安装到本地:

npm install htmx-ext-sse
cp ../node_modules/htmx-ext-sse/dist/sse.min.js ./app/static/app/js/
# ./app/static/app/js/是存放django静态数据的目录,每个人的命名规则或喜好会有些许不同,请根据实际情况修改该目标路径。

接着,在django template中引用该插件:

{% load static %}
<head>
    ...
    <script src="{% static 'app/sse.min.js' %}"></script>
    ...
</head>

配置Celery

关于Celery的安装配置,Celery的官网已经讲得非常具体了,这边会概括并解释一些步骤。

  1. celery.py - 这个是配置Celery的关键文件,通常是新建在settings.py所在目录的文件夹,即项目文件夹下。 celery.py

  2. 在celery.py中, 有两个地方需要特别注意下。

...
app.config_from_object('django.conf:settings', namespace='CELERY')
app.autodiscover_tasks()
...
# settings.py
...
# Celery Configuration
TEST_BROKER_URL = "redis://localhost:6379/0"
TEST_RESULT_BACKEND = "redis://localhost:6379/0"
...

运行celery -A demo_progressbar worker -l INFO --pool=solo来启动celery worker等活来吧!

如何实现progressbar

新建tasks.py

之前说过,task一般都会跟着app,所以我们可以在app目录下面新建一个tasks.py,这样在启动worker的时候会自动扫描所有task,可以从输出日志中找到那些task被识别到了。 tasks

因为我们打算实现一个progressbar,可以用time.sleep来模拟。

@shared_task(bind=True)
def dosomething(self):
    progress = 0
    n = 100
    for i in range(n):
        time.sleep(0.05)
        progress += 1
        self.update_state(state="PROGRESS", meta={"current": progress, "total": n})

调用task

由于tasks.py文件直接新建在app目录中,我们可以在视图函数中引入后直接调用。

...
from .tasks import dosomething

def start(request):
    task = dosomething.delay()
    return render(request, "demo/progressbar.html", {"task_id": task.id})

前端更新progressbar

sse的具体实现,可以参考如何在Django中实现SSEprogressbar.html

<div x-data="{
currentVal: 0 ,
minVal: 0 ,
maxVal: 100,
calcPercentage(min, max, val){return ((val-min)/(max-min))*100}
}" class="progress-bar" @htmx:sse-before-message="currentVal = $event.detail.data;$event.preventDefault()">
    <div hx-ext="sse" sse-connect="{% url 'progress' %}?task={{ task_id }}" sse-swap="status" sse-close="done">
        Contents of this box will be updated in real time.
        <div class="mt-5 mb-5 flex h-2.5 w-full overflow-hidden rounded-md bg-neutral-50 dark:bg-neutral-900"
             role="progressbar" aria-label="default progress bar" :aria-valuenow="currentVal" :aria-valuemin="minVal"
             :aria-valuemax="maxVal">
            <div class="h-full rounded-md bg-black dark:bg-white"
                 :style="`width: ${calcPercentage(minVal, maxVal, currentVal)}%`">
            </div>
        </div>
    </div>
</div>

views.py

...
async def progress(request):
    task_id = request.GET.get("task")
    async def progress_generator():
        while True:
            result = AsyncResult(task_id)
            if result.state == "PROGRESS":
                yield f"event: status\ndata: {result.info['current']}\n\n"
            elif result.state == "SUCCESS":
                yield f"event: status\ndata: 100\n\n"
                yield f"event: done\ndata: {None}\n\n"
                break
            await asyncio.sleep(1)
    return StreamingHttpResponse(progress_generator(), content_type="text/event-stream")
...

Demo

demo

总结

以上就是整个用htmx sse extension和celery一起实现进度条的实现过程,虽然celery的依赖项(broker+backend)我们用Docker版的redis简化了,但还是感觉有些麻烦,截止发文Django 6.0也发布很久了,里面自带的task框架会简化一部分内容,虽然也挺抽象的(没有官方的backend),但聊胜于无,后面有机会的话再聊一聊这个自带的框架。


Share this post on:

下一篇文章
用HTMX和Alpine.JS实现表单提交(Django v6)