# django-tasks: a backport of Django's background task framework for older versions

> django-tasks is a backport of Django's built-in django.tasks framework, giving projects on older Django releases the task decorator, a pluggable backend and a test-friendly dummy backend. It moved the database and RQ backends into separate packages in version 0.12.0, which is a useful signal about where the framework boundary sits.

**RealOrangeOne/django-tasks** — A backport of Django's built in Tasks framework

- Repository: https://github.com/RealOrangeOne/django-tasks
- Website: https://pypi.org/project/django-tasks/
- Stars: 811 · Forks: 54
- Language: Python
- License: BSD-3-Clause
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/realorangeone-django-tasks

## What the backport covers

django-tasks is a backport of django.tasks, which is the Tasks framework built into Django. The repository describes itself exactly that way, and the package is published on PyPI as django-tasks under the BSD 3-Clause licence.

The API is the one Django uses, which is the point of a backport. A task is created with the task decorator:

```python
from django_tasks import task


@task()
def calculate_meaning_of_life() -> int:
    return 42
```

And the decorator accepts arguments to customise the task. There is a priority between minus 100 and 100, where larger numbers are higher priority and zero is the default. There is a queue_name to run the task on a specific queue, and a backend naming the backend this task should use, as defined in the TASKS setting. The documentation also shows a modified task built with the using method:

```python
modified_task = calculate_meaning_of_life.using(priority=10)
```

In addition to those attributes, run_after can be passed to specify a specific time the task should run, and there is a task context mechanism so a running task can know how it was enqueued, which arrives as an argument to the task function.

The point of all this is that the shape of your code does not change when you eventually move to the framework Django ships. That is what makes a backport worth having rather than a homegrown queue wrapper.

## Installation and the backends

The install is a pip install:

```
python -m pip install django-tasks
```

Then django_tasks goes into the installed apps:

```python
INSTALLED_APPS = [
    # ...
    "django_tasks",
]
```

The second configuration step is a backend, and that is what actually connects the tasks to whatever will execute them. If the setting is omitted, the default is:

```python
TASKS = {
    "default": {
        "BACKEND": "django_tasks.backends.immediate.ImmediateBackend"
    }
}
```

So with no configuration the task runs immediately in the current thread, which is convenient for development and useless for production. Two backends are included by default. The dummy backend does not execute the tasks and just stores them, and the documentation says that is especially useful for testing. The immediate backend executes the task immediately in the current thread.

The third-party backends used to be in the box, and that changed in 0.12.0. Prior to that release, django-tasks-db and django-tasks-rq were also included, to provide database and RQ based backends. They are now separate packages, which means the project itself no longer carries a database or a Redis queue implementation, and the framework is the decorator, the settings and the two simple backends.

The release history shows the package is actively worked on, with 0.12.0 in February 2026, 0.11.0 in January 2026 and 0.10.0 in December 2025, and the last push to the repository was on 2026-05-22.

## What a backport is for, and what it costs

The value of a backport is not the library itself, it is the migration path it buys. A project that cannot yet upgrade to the Django version with the built-in tasks framework can adopt the same decorator, the same settings shape and the same backend concept, then when the upgrade happens the change is a version bump rather than a rewrite of every task call site.

The cost is that you now maintain two implementations of the same idea, or at least you depend on a project whose upstream is a different repository. The interesting part of this particular project is that the boundary has been drawn in a defensible place. The core package holds the decorator, the task model, the settings and the simple backends, and the heavyweight execution engines are separate packages maintained alongside it.

That boundary is what a 0.12.0 release signals, and it is worth reading the changelog for your version before you install, because the difference between the 0.11 series and 0.12 is exactly this split. A team that was using the bundled RQ backend has to add a dependency after upgrading, and a team that was using the database backend has to point its settings at a different import path.

The comparison with writing your own decorator is the honest one. A shared task decorator with a queue name and a priority is a small amount of code, and the cost of the backport is that the execution model, the retry behaviour and the visibility of enqueued tasks are all the library's rather than yours. For a project that will upgrade to modern Django eventually, that is a good trade. For a project that has decided to stay put, the maintenance of a second implementation is the price of a nicer API.

## Task context and the run_after timing argument

Two features of the API are worth spelling out because they are the ones you will actually use in a real application.

The first is task context. Sometimes the running task may need to know context about how it was enqueued, and the mechanism is that the context is received as an argument to your task function. That is a small design choice with real consequences: the context is a parameter, so a task that wants it declares it, and a task that does not care simply omits it. The alternative, a global or a thread local, would make tasks that run on a different worker unable to see who queued them.

The second is run_after, which is passed alongside the decorator arguments to specify a specific time the task should run. Combined with the priority and the queue name, that gives you three axes of scheduling: which queue, how urgent, and when. The default priority is zero and the range is minus 100 to 100.

The backend selection per task is the third axis, and it is the one that lets a single project mix execution models. The backend argument names a backend from the TASKS setting, so a task that must not fail can go to the immediate backend and run inline, while a task that is safe to retry goes to a queue. In a backport context, that flexibility is what lets you adopt the framework without committing to one execution model on day one.

## The testing story, which is the strongest argument for a backport

The dummy backend is described in the documentation as not executing the tasks and just storing them, and that is called out as especially useful for testing. It deserves more attention than it usually gets.

With a dummy backend, a test can enqueue a task, assert that it was enqueued, and assert on its arguments, without the task ever running. A test that asserts on a side effect of a running task is a test that depends on a queue, a worker and a timing. A test that asserts on the enqueue is a test that depends on nothing.

The immediate backend covers the other half of the problem. It runs the task in the current thread, so a test can call the task and assert on what it did, without a broker in the way, and the same code path works in production when the setting points at a real backend.

Between the two, a test suite can cover both the enqueue decision and the task body, in the same style, with the backend swapped in configuration. That is a real advantage of a framework that has a pluggable backend at all, and it is a stronger reason to use this backport than the API similarity itself, because the testing benefit lands immediately while the upgrade benefit is realised later.

The default configuration matters here too. With no TASKS setting, the immediate backend is used, which means an unconfigured project runs tasks inline, and a test suite that forgets to set a backend still passes, which is either helpful or dangerous depending on what you were trying to prove.

## Conclusion

django-tasks fits a team on a Django release that predates the built-in tasks framework and wants the same decorator and backend shape so the upgrade later is a version bump. It does not fit a project already on a Django version that ships django.tasks, where the backport is a second implementation to maintain. Before adopting it, check which Django versions your project still supports, because the whole value of a backport is that it lets you upgrade, and read the 0.12.0 changelog note about the database and RQ backends moving to separate packages, because that changes what you install for a real queue.

## FAQ

### What is django-tasks and what does it give me?

It is a BSD licensed backport of Django's built-in django.tasks framework, so a project on an older Django release can use the same task decorator, the same settings and the same backend concept that newer Django ships, and upgrade later without rewriting every call site.

### How do I install and configure django-tasks?

Install it with `python -m pip install django-tasks`, add `django_tasks` to INSTALLED_APPS, and configure a backend in the TASKS setting. If the setting is omitted, the default is the immediate backend, which runs the task in the current thread.

### Which backends does django-tasks include?

Two by default: a dummy backend that stores tasks without executing them, described as especially useful for testing, and an immediate backend that runs the task in the current thread. Prior to version 0.12.0 the database and RQ based backends were also included, but they now live in separate packages.

### What options can the django-tasks decorator take?

A priority between minus 100 and 100 with larger numbers being higher priority and zero the default, a queue_name to run the task on a specific queue, a backend naming which backend the task uses, and run_after to specify a specific time the task should run. A modified task can be built with the using method, for example calculate_meaning_of_life.using(priority=10).

### How current is django-tasks?

The package is on PyPI as django-tasks with releases 0.12.0 from February 2026, 0.11.0 from January 2026 and 0.10.0 from December 2025, and the last push to the repository was on 2026-05-22. The 0.12.0 release is the significant one, because it moved the database and RQ backends out into their own packages.

## Sources

- [License: BSD-3-Clause](https://github.com/RealOrangeOne/django-tasks/blob/master/LICENSE)
- [Project website](https://pypi.org/project/django-tasks/)
- [README](https://github.com/RealOrangeOne/django-tasks/blob/master/README.md)
- [RealOrangeOne/django-tasks on GitHub](https://github.com/RealOrangeOne/django-tasks)
- [Releases](https://github.com/RealOrangeOne/django-tasks/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/realorangeone-django-tasks
