About django-q2
A Django Q2-focused development skill for configuring background jobs, schedules, workers, and brokers. It provides project checks and guidance for task design, testing, deployment, and troubleshooting.
Key features
- 01Check Django Q2 configuration
- 02Design enqueueable and idempotent tasks
- 03Set up named recurring schedules
- 04Choose Redis or ORM brokers
- 05Test tasks and worker integration
- 06Troubleshoot workers and queues
Use cases
- 01Add background jobs to a Django app
- 02Configure schedules and broker settings
- 03Investigate tasks that fail to run
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ lvtd-llc/ skills --skill django-q2After installing, type/django-q2in Claude Code to confirm it loaded.
Details
From the project READMEDjango Q2 Background Jobs
Use this before touching task enqueueing, schedules, worker deployment,
Q_CLUSTER, or code imported by Django Q2 workers.
Configuration Checks
- The dependency is
django-q2; the Python import path isdjango_q. django_qis inINSTALLED_APPS; its migrations provide task result, schedule, and broker models.- Find
Q_CLUSTERin the project's settings module before changing task, worker, or broker behavior. - Confirm the configured broker. Redis is common, often through an environment
variable such as
REDIS_URL; the ORM broker is useful for low-throughput or Redis-free deployments. - Confirm the project's worker command. The base command is
python manage.py qcluster, but projects may wrap it withuv, Poetry, Docker Compose, process managers, or platform-specific worker declarations. - If Redis is removed as the broker, review any Redis-dependent cache, health check, Docker, deployment, and documentation references separately.
Mental Model
- Web code calls
async_task(...)or createsSchedulerows. - The broker stores queued task packages until a
qclusterprocess reserves work. - Worker processes execute importable Python functions and save results or failures.
- The scheduler runs inside the cluster. Schedules are database rows; they do
nothing unless
qclusteris running.
Adding Tasks
- Put the task function in the app that owns the behavior, usually a
tasks.pymodule or another importable module already used by the project. - Keep the function importable at module import time. Do not rely on request objects, local closures, or process-local state.
- Pass durable identifiers such as primary keys, not model instances, open files, connections, or large payloads.
- Make the task idempotent. Redis does not give exactly-once execution guarantees, and receipt-based brokers can re-run work.
- If the task depends on a just-saved database row, enqueue it from
transaction.on_commit(...).
def send_welcome_email(user_id: int) -> None:
from django.contrib.auth import get_user_model
user = get_user_model().objects.get(pk=user_id)
...
from django.db import transaction
from django_q.tasks import async_task
transaction.on_commit(
lambda: async_task("myapp.tasks.send_welcome_email", user.pk)
)
Use q_options when Django Q2 options would collide with task kwargs:
async_task(
"myapp.tasks.rebuild_report",
report_id,
q_options={"timeout": 300, "group": "reports"},
)
Scheduling Work
Prefer named, idempotent schedules created by a migration, admin action, or setup command. Avoid creating schedules unconditionally at import time or app startup.
from django_q.models import Schedule
Schedule.objects.get_or_create(
name="clear-expired-sessions",
defaults={
"func": "django.core.management.call_command",
"args": "'clearsessions'",
"schedule_type": Schedule.HOURLY,
},
)
Use Schedule.objects.get_or_create(name=..., defaults={...}) when seeding
schedules so repeated setup does not duplicate jobs. Cron schedules require the
optional croniter dependency; do not use Schedule.CRON unless the project
includes it.
Missed schedules catch up by default. Set Q_CLUSTER["catch_up"] = False when
a job should run once after downtime instead of replaying every missed interval.
Broker Choices
Redis Broker
Use Redis when the project already depends on it for workers or deployment:
Q_CLUSTER = {
"name": "...",
"timeout": 3600,
"workers": 4,
"redis": REDIS_URL,
}
Redis is fast and usually fits projects that already run Redis for cache, Docker, or deployment workers. The default Redis broker does not support delivery receipts. If a worker host dies catastrophically while executing a task, the in-flight package can be lost; if task code raises, Django Q2 records a failure. Use idempotent task design, explicit retries in task code where needed, and monitoring for failures.
ORM Broker
Use the Django database broker only for low-throughput deployments, local simplicity, or environments where Redis is unavailable:
Q_CLUSTER = {
"name": "...",
"timeout": 3600,
"retry": 4800,
"workers": 4,
"max_attempts": 2,
"orm": "default",
}
When switching to ORM:
- Remove the
"redis"broker key; configure one broker per cluster unless you intentionally use custom clusters. - Run migrations for
django_q. If the broker uses a non-default database alias, run migrations with--database <alias>. - Increase
"poll"above the default0.2seconds, for example"poll": 2.0, when you need lower database polling pressure and can tolerate higher queue pickup latency. - The ORM broker enables the Queued Tasks admin table.
- Review Redis-dependent cache, health check, Docker, and deployment settings separately. Schedules are always database rows; the broker setting controls queued task packages, not the schedule table.
Testing
- Test task business logic by calling the function directly.
- Test enqueueing with synchronous mode:
- per call:
async_task("myapp.tasks.fn", arg, sync=True) - per test: override
Q_CLUSTER["sync"] = True
- per call:
- For worker/broker integration, run
the project's
qclustercommand in a separate process and wait forresult(task_id, 200)or a similar bounded wait; do not rely on arbitrary sleeps. - Use
pytest.mark.django_db(transaction=True)when a real worker process must observe committed database rows.
Debugging Checklist
- Is a
qclusterprocess running with the same settings module,SECRET_KEY, broker URL, and cluster name as the web process? - Can the worker import the dotted task path?
- Did database migrations run, including
django_qmigrations? - Is Redis reachable from both web and worker containers, or is the ORM broker polling the expected database?
- Did a scheduled task duplicate because setup created another
Schedulerow with no stable name? - Did downtime trigger schedule catch-up?
- Is task failure visible in Django admin, logs, or the configured error reporter?
References
- Official docs: https://django-q2.readthedocs.io/en/master/
- Upstream repo: https://github.com/django-q2/django-q2