Metadata-Version: 2.4
Name: django-command-center
Version: 0.1.0
Summary: Run Django management commands from the admin: background runs, live logs, cancel, history - choose which commands in settings or in code.
Author: silgena
License-Expression: BSD-3-Clause
Project-URL: Homepage, https://github.com/silgena/django-command-center
Project-URL: Source, https://github.com/silgena/django-command-center
Project-URL: Issues, https://github.com/silgena/django-command-center/issues
Classifier: Framework :: Django
Classifier: Framework :: Django :: 5.2
Classifier: Framework :: Django :: 6.0
Classifier: Programming Language :: Python :: 3
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: Django>=5.2
Provides-Extra: tasks
Requires-Dist: django-tasks>=0.8; extra == "tasks"
Dynamic: license-file

# django-command-center

Run Django management commands from the admin - without SSH.

- **Every command, found automatically.** Your project's commands and Django's
  own appear by themselves; you decide which ones, in settings or in the
  command's code.
- **Runs in the background, in its own process.** A command that crashes,
  leaks memory or hangs cannot take your worker down.
- **Live output.** Watch it as it prints; download the full log afterwards.
- **Cancel and time limits that work.** The process is stopped, children and
  all.
- **History.** Who ran what, with which arguments, when, for how long, and how
  it ended.
- **Permissions.** Running and cancelling are model permissions; a command can
  require one more of its own.

Background runs go through `django.tasks` (Django 6.0+) or the
[django-tasks](https://pypi.org/project/django-tasks/) backport, or are
queued for `manage.py command_center_worker`.

## Install

```bash
pip install django-command-center            # Django 6.0+
pip install "django-command-center[tasks]"   # Django 5.2, with background runs
```

```python
INSTALLED_APPS = [..., "command_center"]
```

```bash
python manage.py migrate command_center
```

Then open **Command center > Command runs > Run a command** in the admin.

## Choose the commands

Every installed command is offered except the defaults in `EXCLUDE`: servers,
shells, interactive commands, code generators, and `flush`.

```python
from command_center.conf import DEFAULT_EXCLUDE

COMMAND_CENTER = {
    # Only these (fnmatch patterns). None offers everything not excluded.
    "INCLUDE": None,
    # Never offered - extend the defaults rather than replace them.
    "EXCLUDE": [*DEFAULT_EXCLUDE, "send_newsletter"],
    # Extra groups on the run page, besides one per app.
    "TAGS": {"Reports": ["check", "showmigrations", "report_*"]},
    # Per command. Naming an excluded command here offers it anyway.
    "COMMANDS": {
        "migrate": {"confirm": "Applies migrations to this database.", "timeout": 600},
        "clearsessions": {"tags": ["Maintenance"]},
        "rebuild_index": {"permission": "search.change_index", "timeout": 7200},
    },
}
```

Or describe a command where it is written; settings override it:

```python
class Command(BaseCommand):
    command_center = {"tags": ["Reports"], "timeout": 120}

    # or keep it out of the admin entirely:
    # command_center = False
```

| Option | |
|---|---|
| `confirm` | A warning the user must accept before it runs |
| `timeout` | Seconds before it is stopped (default `TIMEOUT`, 3600) |
| `permission` | `"app_label.codename"` needed to see and run it |
| `tags` | Groups it appears under |
| `description` | Replaces the command's `help` on the run page |

## Settings

| Setting | Default | |
|---|---|---|
| `TIMEOUT` | `3600` | Default time limit in seconds |
| `STORAGE` | `"default"` | Alias in `STORAGES` full logs are saved to |
| `LOCATION` | `"command_center/"` | Folder within that storage |
| `LOG_TAIL_CHARS` | `20000` | Output kept on the run for the live view |
| `HEARTBEAT_SECONDS` | `2` | How often a run reports progress |
| `LOST_AFTER_SECONDS` | `120` | Silence after which a run counts as lost |
| `RETENTION_DAYS` | `90` | Used by `command_center_prune` |
| `RUNNER` | auto | `"tasks"` or `"worker"` |
| `TASK_BACKEND` | `"default"` | Task backend alias for `"tasks"` |

## Permissions

- `command_center.add_commandrun` - run commands
- `command_center.cancel_commandrun` - cancel runs
- `command_center.view_commandrun` - see the history and logs
- plus any `permission` a command declares

## How a command runs

Each run starts `python -m django <command> <arguments>` with the same
settings module and import path as the worker. Its standard input is closed:
a command that asks a question fails at once instead of waiting forever, so
pass `--no-input` where a command supports it. Output is read line by line;
every `HEARTBEAT_SECONDS` the run's page gets the latest of it, and a cancel
or an expired time limit stops the process group with SIGTERM, then SIGKILL.
A run whose worker dies is marked lost after `LOST_AFTER_SECONDS`.

## Without a task framework

```python
COMMAND_CENTER = {"RUNNER": "worker"}
```

```bash
python manage.py command_center_worker          # keeps polling
python manage.py command_center_worker --once   # from cron
```

## Housekeeping

```bash
python manage.py command_center_prune   # deletes runs older than RETENTION_DAYS
```

## Development

```bash
python runtests.py
```

## License

BSD-3-Clause.
