Custom Runner¶
Any third-party queue (Django-RQ, Huey, Dramatiq, …) can be used by
implementing BaseCommandRunner.
Example: Django-RQ¶
# myproject/runners.py
import django_rq
from django.urls import reverse
from django_admin_runner.models import CommandExecution
from django_admin_runner.runners import BaseCommandRunner, RunResult
from django_admin_runner.tasks import execute_command
class RqCommandRunner(BaseCommandRunner):
backend = "rq"
def run(self, command_name, kwargs, triggered_by, execution) -> RunResult:
execution.backend = self.backend
execution.save(update_fields=["backend"])
job = django_rq.enqueue(execute_command, command_name, kwargs, execution.pk)
execution.task_id = job.id
execution.save(update_fields=["task_id"])
return RunResult(
execution=execution,
redirect_url=reverse(
"admin:django_admin_runner_commandexecution_change",
args=[execution.pk],
),
is_async=True,
backend=self.backend,
task_id=job.id,
)
# settings.py
ADMIN_RUNNER_BACKEND = "myproject.runners.RqCommandRunner"
RQ_QUEUES = {"default": {"HOST": "localhost", "PORT": 6379}}
Key points¶
- Set
execution.backendbefore saving so the record reflects the runner used. - Use
execute_commandfromdjango_admin_runner.tasks— it handles status updates, stdout/stderr capture, and timestamps. - Return a
RunResultwith a validredirect_url(usually the execution detail page). - Set
is_async=Trueif the command runs in a worker process.
Optional capabilities¶
BaseCommandRunner declares several opt-in capabilities. Override them if
your backend supports them — the admin adapts automatically:
| Attribute / method | Effect |
|---|---|
supports_max_retries = True |
Enables per-command max_retries= instead of warning |
supports_force_stop = True |
Shows the Force Stop control once a stop was requested |
stop(execution) |
Request a graceful stop (base: sets the stop_requested flag; add a SIGTERM-equivalent if you can target the worker) |
force_stop(execution) -> bool |
Hard-kill the worker process; return True when performed |
finalize_stale(execution) |
Attribute why a dead worker's execution ended — return (status, note) (e.g. ("TIMEOUT", …)) or None |
supported_schedule_kinds |
Schedule kinds your backend can materialize natively (e.g. frozenset({"cron", "interval", "clocked"})) — unhides the scheduling UI |
create_schedule / update_schedule / delete_schedule / schedule_next_run |
Materialize ScheduledCommand rows into your backend's native periodic tasks |
See django_q2.py for a complete implementation of all of the above, and the Scheduling guide for the row/native-object lifecycle.