# How It Works

The core idea: **argparse introspection → dynamic form generation → command execution**.

## Data flow

```mermaid
graph LR
    A[discover_commands] --> B[introspect_command]
    B --> C[build_command_form]
    C --> D[execute_command]
    D --> E[CommandResult]
```

## Step by step

### 1. Discovery

`discover_commands()` finds all registered management commands via `django.core.management.get_commands()` and returns a list of `CommandInfo` dataclasses.

### 2. Introspection

`introspect_command()` loads a command's `ArgumentParser` and parses its `_actions` into `ArgumentInfo` dataclasses. Each `ArgumentInfo` captures the argument's name, type, help text, default, choices, and whether it's required.

Results are cached with `@lru_cache` for performance.

### 3. Form generation

`build_command_form()` maps each `ArgumentInfo` to a Django form field:

| Argument type | Form field |
|---|---|
| `store_true` / `store_false` | `BooleanField` (checkbox) |
| `choices` | `ChoiceField` (dropdown) |
| `type=int` | `IntegerField` |
| `type=float` | `FloatField` |
| `type=Path` | `PathField` (file upload + text input toggle) |
| default / `type=str` | `CharField` |

Arguments are displayed in two groups:

- **Command-specific arguments** — shown prominently
- **BaseCommand options** (`--verbosity`, `--traceback`, etc.) — in a collapsible section

### 4. Execution

`execute_command()` runs the command via Django's `call_command()`, capturing stdout and stderr. It measures execution time and returns a `CommandResult` dataclass.

Commands run with a configurable timeout (default 30s) in a separate thread. Large outputs are automatically truncated to head + tail lines. See [Configuration](configuration.md) for details.

### 5. Result display

The result page shows:

- Command name and arguments used
- stdout output
- stderr output (if any)
- Execution duration
- Success/error status with appropriate styling

## Working with Path arguments

If your management command uses `type=pathlib.Path` for any argument, the form renders a special **PathField** widget with two input modes:

### File upload (default)

The user selects a file from their local machine. The file is:

1. Streamed in chunks to a temporary file in `/tmp/` (memory-safe for large files)
2. The temp file path is passed as a string argument to `call_command()`
3. After command execution (success or failure), the temp file is **always deleted**

This is the primary mode — useful when the command expects to read a data file (CSV import, fixture loading, etc.).

### Text path input

Click "or enter path as text" to switch to a plain text input. The user types a filesystem path directly (e.g. `/data/exports/report.csv`). No file is uploaded or created — the path string is passed to the command as-is.

This is useful when the file already exists on the server or when the argument is an output path where the command will write its results.

### How it works internally

```
PathField (form) → UploadedFile or str
    ↓
_temporary_uploaded_files (context manager)
    ↓ if UploadedFile:
    save to /tmp/<unique-name> via chunks()
    replace form value with path string
    ↓
call_command(..., path="/tmp/abc123")
    ↓
finally: delete temp file
```

### Defining Path arguments in your command

```python
# myapp/management/commands/import_data.py
import pathlib
from django.core.management.base import BaseCommand

class Command(BaseCommand):
    def add_arguments(self, parser):
        parser.add_argument('input_file', type=pathlib.Path, help='CSV file to import')
        parser.add_argument('--config', type=pathlib.Path, required=False, help='Config file')
```

Both `pathlib.Path` and `pathlib.PurePath` subclasses are detected automatically.

## Key dataclasses

- **`CommandInfo`** — command name, app label, help text
- **`ArgumentInfo`** — argument name, type, default, choices, help, required flag
- **`CommandResult`** — stdout, stderr, return code, duration

## Architecture notes

- **No database models** — everything is derived from argparse at runtime
- **No migrations** — nothing to migrate
- **Caching** — introspection results are cached with `@lru_cache`
- **Superuser-only** — all views require `is_superuser`
- **Command allowlist** — visible commands can be restricted via `MANAGEMENT_UI["COMMANDS"]`
- **Execution timeout** — commands are killed after configurable timeout (default 30s)
- **Output truncation** — large outputs are trimmed to prevent browser overload
