skip to content

Celery Wiring

Wiring Celery into Django means a celery.py app reading CELERY-namespaced settings and autodiscovering task modules. Interviewers probe passing IDs, not instances, and enqueueing on commit.

part ofDjangooverview, primer and where to startread it →
on this pageshow

explore

questions

5

How do you wire Celery into a Django project, and what goes into celery.py and the project package's __init__.py?

level: juniorimportance: must knowfreq 58%

answer

  1. one app instance per project
  2. settings module before the app
  3. configuration read from Django settings
  4. discover each app's tasks module

basics

~10 s

Create proj/celery.py that sets DJANGO_SETTINGS_MODULE, builds Celery('proj'), calls config_from_object('django.conf:settings', namespace='CELERY') and autodiscover_tasks(); then import that app in proj/init.py so it loads whenever Django starts.

solid answer

~30 s

Celery documents a small, fixed recipe. In the project package next to `settings.py` I add `celery.py`: first `os.environ.setdefault("DJANGO_SETTINGS_MODULE", "shop.settings")`, then `app = Celery("shop")`, then `app.config_from_object("django.conf:settings", namespace="CELERY")` so all Celery options live in `settings.py` as `CELERY_*` names, then `app.autodiscover_tasks()` so every installed app's `tasks.py` is imported. In `shop/__init__.py` I add `from .celery import app as celery_app` so the app exists whenever Django starts and `@shared_task` functions bind to it. Tasks live in each app's `tasks.py`, and the worker runs as its own process with `celery -A shop worker -l INFO`.

code

python · 9 lines
python
import os

from celery import Celery

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "shop.settings")

app = Celery("shop")
app.config_from_object("django.conf:settings", namespace="CELERY")
app.autodiscover_tasks()

go deeper

for a junior

Recall the two files: celery.py with the four setup lines, and the import in the project package's init.py, plus a worker started as its own process.

for a middle

Explain why the settings module must be set first, what the CELERY namespace does to setting names, and how autodiscover_tasks finds each app's tasks module.

for a senior

Explain what the Django fixup does in the worker, such as django.setup() and closing database connections around tasks, and diagnose a worker that cannot load models.

for a principal

Decide whether a project standardises on Celery or a backend behind Django's own task API, and who owns worker deployment and configuration.

## What "wiring" means **Celery** is a separate task-queue library with its own worker processes. It needs no Django-specific package any more; you connect it to a Django project with a few lines of configuration so that: - the worker process can load Django (settings, models, the ORM), - Celery reads its options from Django's `settings.py`, - tasks defined in each Django app are found and registered, - code running inside Django (views, signals) can queue tasks. ## The celery.py module For a project laid out as `shop/manage.py` and `shop/shop/settings.py`, the recommended place is `shop/shop/celery.py`: ```python import os from celery import Celery os.environ.setdefault("DJANGO_SETTINGS_MODULE", "shop.settings") app = Celery("shop") app.config_from_object("django.conf:settings", namespace="CELERY") app.autodiscover_tasks() ``` Line by line: 1. **`DJANGO_SETTINGS_MODULE`** — the `celery` command is not `manage.py`, so nothing else tells it which settings to load. It must be set **before** `Celery(...)` is created: Celery installs its Django integration (the "fixup") at app creation only when this environment variable is present. `setdefault` keeps an explicit value from the environment. 2. **`Celery("shop")`** — the single Celery application for the project. Task names still default to the module path of each function, such as `orders.tasks.send_order_confirmation`. 3. **`config_from_object("django.conf:settings", namespace="CELERY")`** — use Django's settings as Celery's configuration. The string form means the worker does not need to serialise the settings object to child processes. The namespace means Celery reads **uppercase names prefixed with `CELERY_`**: `broker_url` becomes `CELERY_BROKER_URL`. 4. **`autodiscover_tasks()`** — with no arguments, Celery asks its Django integration for every installed app's config and imports `<app>.tasks` from each. Discovery is lazy by default and happens when the worker imports its modules. ## The project package's __init__.py ```python from .celery import app as celery_app __all__ = ("celery_app",) ``` This import makes the Celery app exist as soon as Django imports the project package. Without it, a web process might import a module using `@shared_task` before any app exists, and the task would not be bound to your configured app. ## Where tasks and settings go | Piece | Location | |---|---| | Celery app | `shop/celery.py` | | App import at startup | `shop/__init__.py` | | Celery options | `settings.py`, as `CELERY_BROKER_URL`, `CELERY_RESULT_BACKEND`, `CELERY_TASK_TIME_LIMIT`, ... | | Tasks | each app's `tasks.py`, decorated with `@shared_task` | | Worker | separate process: `celery -A shop worker -l INFO` | ## Order-confirmation example ```python # orders/tasks.py from celery import shared_task from django.core.mail import send_mail from .models import Order @shared_task def send_order_confirmation(order_id): order = Order.objects.select_related("customer").get(pk=order_id) send_mail( f"Order {order.pk} confirmed", "Thanks for your order.", None, [order.customer.email], ) ``` The view queues it with `send_order_confirmation.delay(order.pk)`. ## Common wiring mistakes | Mistake | Symptom | |---|---| | `DJANGO_SETTINGS_MODULE` set after `Celery(...)` | Worker cannot import models; no `delay_on_commit()` | | Celery options without the `CELERY_` prefix | Options silently ignored; defaults used | | Task defined outside the app's `tasks.py` | Worker reports an unregistered task | | No import in the project package's `__init__.py` | Web process queues through an unconfigured default app | | Worker started with other settings than the web process | Tasks read another database or broker | The last one is easy to miss in deployment: the web server and the worker are two processes with two environments, so the settings module and secrets must be set for both. ## What the Django integration does for you Once `DJANGO_SETTINGS_MODULE` is set, Celery's Django fixup calls `django.setup()` before task modules are imported in the worker, closes (or periodically recycles) database connections around each task so stale connections are not reused, and, unless you configured your own task base class, makes tasks use a Django-aware class that adds `delay_on_commit()`. These are the reasons the order of lines in `celery.py` matters.

  • Why must os.environ.setdefault('DJANGO_SETTINGS_MODULE', ...) come before Celery('shop')?
    Celery decides whether to install its Django fixup when the app is created, by checking that environment variable. If it is missing at that moment, the worker never calls `django.setup()` for you and has no Django-aware task class, so importing task modules that touch models fails with settings or app-registry errors.
  • What breaks if you forget the import in the project package's __init__.py?
    The Celery app is only created when something imports `shop.celery`. The worker usually does, because `-A shop` looks for the app there, but the web process may not, so `@shared_task` functions imported by views resolve to Celery's default app with default configuration instead of yours.

saying these in an interview costs you the question

  • Thinks Celery tasks run inside the Django web process
  • Sets DJANGO_SETTINGS_MODULE after creating the Celery app
  • Keeps a separate celery config file duplicating Django settings without reason
  • Forgets to import the Celery app in the project package's __init__.py
  • Believes a separate django-celery package is still required
open as a page

A Django view creates an order in a transaction and queues a Celery email task that intermittently raises Order.DoesNotExist; what is wrong?

level: seniorimportance: must knowfreq 60%

basics

~20 s

The task is queued before the transaction commits, so the worker's separate connection may not see the order yet. Queue it with transaction.on_commit(), or Celery's delay_on_commit() on Django projects, so the message is sent only after commit.

open as a page

A Django project's Celery worker ignores BROKER_URL in settings.py; how does namespace='CELERY' in config_from_object explain it?

level: middleimportance: should knowfreq 35%

basics

~10 s

With namespace='CELERY', Celery reads each option as an uppercase CELERY_-prefixed Django setting: broker_url becomes CELERY_BROKER_URL. A plain BROKER_URL or a lowercase broker_url in settings.py is never read, so the default broker is used.

open as a page

In a Django project using Celery, why do apps define tasks with @shared_task instead of @app.task, and how are they discovered?

level: middleimportance: should knowfreq 45%

basics

~20 s

@shared_task creates a task without importing a concrete Celery app, so a Django app never imports the project's celery module; autodiscover_tasks() then imports each installed app's tasks.py so the task is registered with the project's app.

open as a page

Why should a Django view pass an order's primary key to a Celery task rather than the Order instance itself?

level: seniorimportance: should knowfreq 55%

basics

~20 s

Celery serialises task arguments, JSON by default, and a Django model instance is not JSON-serialisable. Even with pickle, an instance is a stale snapshot; passing order.pk lets the task load current data and handle a deleted row.

open as a page