Tasks on Django 5 and Django 6

Django 5.2 uses the optional django-tasks backport; Django 6 uses django.tasks. Both already provide aenqueue(). No aiodrf runtime wrapper, import alias or worker is needed. This integration consists of an optional dependency, executable examples and the same request/transaction contract tests.

Runtime Application import Immediate backend
Django 5.2 from django_tasks import task django_tasks.backends.immediate.ImmediateBackend
Django 6+ from django.tasks import task django.tasks.backends.immediate.ImmediateBackend

Install django-aiodrf[tasks] only for the backport. The tested backport line is 0.12.x; the extra deliberately has an upper bound because it is pre-1.0. Its import currently calls django_stubs_ext.monkeypatch() for typing compatibility. aiodrf does not add a patch or import the backport automatically. The default installation does not acquire this dependency.

from django_tasks import task  # Django 5; use django.tasks on Django 6.


@task
def summarize(article_id):
    # Application-owned synchronous work; the selected backend owns execution.
    return {"article_id": article_id}


# Inside an async view:
# result = await summarize.aenqueue(article.pk)

Set TASKS with the matching backend namespace. Do not configure django.tasks... on Django 5 or silently switch namespaces with an import shim. The Django 5 example and Django 6 example are independent uv projects. Each includes validated input, async/sync task functions, a dummy queue and HTTP tests.

ImmediateBackend executes before the response and is not a background worker. DummyBackend records tasks without executing them and is not durable. For production select a maintained backend with the required worker, retries, result retention, authorization and operational monitoring. Enqueueing is not proof of eventual execution. These are the upstream Tasks API responsibilities and the backport's documented contract.

When a task reads newly written rows, register synchronous enqueue() with transaction.on_commit() inside the same synchronous save unit. Never hold transaction.atomic() across arbitrary awaits. Rollback must discard the callback; a queue failure after commit cannot undo the committed row. Use a transactional outbox if atomic database/queue delivery is required.

Under both ASGI and WSGI, synchronous task work stays off the event loop, async tasks are awaited, tasks sent to a queue are not run in the request, calling the synchronous API from async code triggers Django's safety check, and callbacks registered with on_commit enqueue only when the transaction commits. How reliably tasks are delivered depends on the broker and worker you choose.