Celery Progress Bars for Django
Celery Progress (celery-progress) is a Python library that provides drop-in, configurable, dependency-free progress bars for Django/Celery applications. It enables real-time progress updates for long-running asynchronous tasks directly in the web UI. Currently at version 0.5, it is actively maintained and offers simple integration with Django's URL routing and Celery's task management, including support for group results.
Common errors
-
NoReverseMatch at /celery-progress/<task_id>/ Reverse for 'task_status' with arguments '('',)' not found. 1 pattern(s) tried: [u'celery_progress/(?P<task_id>[\w-]+)/$']cause The `task_id` variable is either empty, `None`, or not correctly passed from the Django view to the template, or the URL configuration for `celery_progress` is incorrect or missing.fixEnsure `result.task_id` is successfully obtained from `.delay()` and passed to the template context. Verify that `path('celery-progress/', include('celery_progress.urls'))` or `re_path(r'^celery-progress/', include('celery_progress.urls'))` is correctly set up in your project's `urls.py` for modern Django versions. -
TypeError: sequence item 1: expected a bytes-like object, NoneType found
cause This can occur if the Celery result backend is not properly configured, leading to issues with task state serialization or retrieval, or if the `set_progress` arguments are incorrect.fixDouble-check your `CELERY_RESULT_BACKEND` setting in Django. Ensure it's correctly pointing to a live broker/backend. Also, verify that `progress_recorder.set_progress(current, total, ...)` is always called with valid, non-None `current` and `total` values that are serializable (e.g., integers or floats). -
Progress bar does not update, stuck at 0% or 'Waiting for progress to start...' message.
cause This often indicates a communication breakdown: either the Celery worker isn't running, the task is failing silently, the result backend isn't storing updates, or the frontend JavaScript isn't correctly polling the status URL.fix1. Verify Celery workers are running and processing tasks. 2. Check Celery logs for task errors. 3. Confirm `CELERY_RESULT_BACKEND` is configured and accessible. 4. Ensure the `task_id` passed to `CeleryProgressBar.initProgressBar()` is correct and the URL path is accessible via your browser's developer tools (network tab). -
DOM only updating last element of the loop while displaying multiple progress bars.
cause When rendering multiple progress bars on a single page, the JavaScript might target elements by a non-unique ID, causing only the last element to reflect updates.fixEnsure each progress bar and its associated message/result elements in your HTML template have unique IDs. Adapt your JavaScript initialization to iterate over task IDs and initialize `CeleryProgressBar` for each unique set of elements.
Warnings
- breaking Changed URL pattern definition for Django versions >= 4.0. The library switched from `django.conf.urls.url` to `django.urls.re_path`.
- gotcha The default `get_progress` endpoint for `celery-progress` is publicly accessible, allowing anyone to query task statuses by `task_id`. This can pose a security risk.
- gotcha Celery requires a properly configured result backend (e.g., Redis or RabbitMQ) for `celery-progress` to function correctly. Without it, task progress and results cannot be retrieved.
- gotcha When using Celery's RPC result backend (default in some setups), `celery-progress` may fail to pull all task states for multiple concurrent tasks because the RPC backend only allows results to be retrieved once by the client that initiated the task.
Install
-
pip install celery-progress -
pip install celery-progress[websockets,redis] -
pip install celery-progress[websockets,rabbitmq]
Imports
- ProgressRecorder
from celery_progress.backend import ProgressRecorder
- GroupProgress
from celery_progress.backend import GroupProgress
- shared_task
from celery import shared_task
Quickstart
import os
import time
from celery import shared_task
from celery_progress.backend import ProgressRecorder
# settings.py
# INSTALLED_APPS = [
# ...,
# 'celery_progress',
# ]
# CELERY_BROKER_URL = os.environ.get('CELERY_BROKER_URL', 'redis://localhost:6379/0')
# CELERY_RESULT_BACKEND = os.environ.get('CELERY_RESULT_BACKEND', 'redis://localhost:6379/0')
# urls.py (in your project's main urls.py)
# from django.urls import path, include
# urlpatterns = [
# ...,
# path('celery-progress/', include('celery_progress.urls')),
# ]
@shared_task(bind=True)
def my_long_task(self, total_steps):
progress_recorder = ProgressRecorder(self)
for i in range(total_steps):
time.sleep(1) # Simulate work
progress_recorder.set_progress(i + 1, total_steps, description=f'Step {i+1} of {total_steps}')
return 'Task Completed!'
# views.py
# from django.shortcuts import render
# from .tasks import my_long_task
# def start_task_view(request):
# if request.method == 'POST':
# result = my_long_task.delay(10) # Start the task asynchronously
# return render(request, 'task_progress.html', {'task_id': result.task_id})
# return render(request, 'start_task.html')
# task_progress.html (template)
# <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>
#
# {% load static %}
# <script src="{% static 'celery_progress/celery_progress.js' %}"></script>
# <script>
# document.addEventListener("DOMContentLoaded", function() {
# var progressUrl = "{% url 'celery_progress:task_status' task_id %}";
# CeleryProgressBar.initProgressBar(progressUrl, {
# onSuccess: function(result, elm) {
# document.getElementById('celery-result').innerHTML = 'Result: ' + result;
# }
# });
# });
# </script>