In Django 6.0 or later, how do you define a thumbnail job with the @task decorator and enqueue it from a view?
answer
- a decorator from django.tasks
- module-level function
- the decorated name is not a function
- enqueue() returns a result object
basics
~20 sDecorate a module-level function with @task from django.tasks, then call make_thumbnail.enqueue(photo.pk) in the view; enqueue() hands the call to the configured TASKS backend and returns a TaskResult instead of running the function in the view.
solid answer
~30 sIn Django 6.0+ I put `@task` from `django.tasks` on a module-level function in the app's `tasks.py`, for example `make_thumbnail(photo_id)`. The decorator returns a `Task` object, not a function, so the view calls `make_thumbnail.enqueue(photo.pk)`; calling `make_thumbnail(photo.pk)` directly raises `TypeError`. `enqueue()` validates the task against the backend named in the `TASKS` setting, stores the call with JSON-serialisable arguments, and returns a `TaskResult` with an `id` and a `status`. `aenqueue()` is the variant for async views. What runs it depends on the backend: the default `ImmediateBackend` runs it straight away in the same thread, and production needs a third-party backend with a worker.
code
python · 14 linesfrom django.tasks import task
from .imaging import render_thumbnail
from .models import Photo
@task(queue_name="default")
def make_thumbnail(photo_id):
render_thumbnail(Photo.objects.get(pk=photo_id))
# in a view, after photo = form.save():
# result = make_thumbnail.enqueue(photo.pk)
# result.id, result.statusgo deeper
Recall the three moves: @task on a module-level function, enqueue() with simple arguments, and the TaskResult that comes back.
Explain what enqueue() validates, why arguments must survive a JSON round-trip, and why the default backend still runs the work inside the request.
Make sure the task re-fetches its data by primary key and that a production backend with a worker is configured before relying on it for latency.
Decide whether to standardise on django.tasks as the project's task API so the queue backend can be swapped without touching view code.
## What django.tasks is Django 6.0 added the **Tasks framework** in `django.tasks`. It gives Django a standard way to **define** a piece of work and **enqueue** it to run outside the request-response cycle. It deliberately does not include a **worker** — the process that picks up queued work and runs it. Django handles definition, validation, queuing and result handling; execution belongs to a **backend** configured in the `TASKS` setting. (Before 6.0 the same API was available as a separate backport package.) ## Defining the task ```python # photos/tasks.py from django.tasks import task from .models import Photo from .imaging import render_thumbnail @task def make_thumbnail(photo_id): photo = Photo.objects.get(pk=photo_id) render_thumbnail(photo) ``` Rules the framework enforces or recommends: - The function must be defined **at module level**. A nested function or a lambda raises `InvalidTask` ("Task function must be defined at a module level"), because a worker must be able to import it by path. - By convention tasks live in `tasks.py`; this is not enforced. - `@task` can be used bare or with options: `@task(priority=2, queue_name="images")`. - The decorator returns a **`Task` instance**, a frozen object that wraps the function. It is not callable. ## Enqueueing it from a view ```python # photos/views.py from django.shortcuts import redirect from .forms import PhotoForm from .tasks import make_thumbnail def upload_photo(request): form = PhotoForm(request.POST, request.FILES) if form.is_valid(): photo = form.save() make_thumbnail.enqueue(photo.pk) return redirect("photos:detail", pk=photo.pk) ... ``` `enqueue(*args, **kwargs)`: 1. Validates the `Task` against its backend (queue name, priority, deferral support). An invalid combination raises `InvalidTask`. 2. Builds a `TaskResult` whose arguments are normalised to JSON-compatible values; a model instance or `datetime` raises `TypeError` here. 3. Hands it to the backend and returns the `TaskResult`, which has an `id`, a `status` (`READY`, `RUNNING`, `FAILED` or `SUCCESSFUL`) and timestamps. In an `async def` view, use `await make_thumbnail.aenqueue(photo.pk)`. ## The mistake interviewers look for | Call | What happens | |---|---| | `make_thumbnail(photo.pk)` | `TypeError`: a `Task` object is not callable | | `make_thumbnail.enqueue(photo.pk)` | Queued through the configured backend | | `make_thumbnail.enqueue(photo)` | `TypeError`: a model instance is not JSON-serialisable | | `@task` on a function nested in the view | `InvalidTask`: must be module level | ## What happens next depends on TASKS With no `TASKS` setting, Django uses `ImmediateBackend`, which **runs the task straight away, in the same thread, inside `enqueue()`**. That is convenient while adopting the API, but the thumbnail is still rendered during the request. Real background execution needs a third-party backend that provides a durable queue and a worker process; you point `TASKS["default"]["BACKEND"]` at its import path, and the view code does not change. ## What enqueue() does not do for you The API is intentionally small, so several things a queue library might do implicitly are left to you or to the backend: - **It does not wait for the database transaction.** If the photo row was created in an open transaction, a worker on another connection may not see it yet; enqueueing after commit is the documented pattern. - **It adds no retries.** Django records failures on the `TaskResult`; whether a failed task is retried is a backend feature. - **It does not deduplicate.** Calling `enqueue()` twice for the same photo queues two thumbnail jobs, so the task itself should be safe to run twice (for example, overwriting the same thumbnail file). - **It does not pick a backend per environment.** The same code runs inline under `ImmediateBackend` in development and in a worker in production, which is why tests should also exercise the real serialisation rules. ## Summary - `@task` turns a module-level function into a `Task`. - `enqueue()` / `aenqueue()` queue it and return a `TaskResult`. - Pass primary keys, not objects. - The backend in `TASKS` decides whether anything actually runs in the background.
- How would you enqueue the same task from an async Django view?Use `await make_thumbnail.aenqueue(photo.pk)`. It takes the same arguments and returns the same `TaskResult`; the backend base class runs the synchronous `enqueue()` through `sync_to_async()` unless the backend provides a native async implementation.
- Why must a Django task function be defined at module level?A task is stored as a reference to its function's import path so that a separate worker process can import and call it. A nested function or lambda has no importable path, so the backend's validation raises `InvalidTask` when the `Task` is created.
saying these in an interview costs you the question
- Calls the decorated task like a normal function and expects it to be queued
- Believes Django 6.0 ships a worker that runs enqueued tasks
- Passes the Photo model instance straight to enqueue()
- Defines the task inside the view function
- Assumes enqueue() returns the task function's return value