Metadata-Version: 2.4
Name: dj-control-room-base
Version: 0.1.1
Summary: Core framework for Django Control Room panels
Author-email: Yasser Toruno <your.email@example.com>
Maintainer-email: Yasser Toruno <your.email@example.com>
License: MIT
Project-URL: Homepage, https://yassi.github.io/dj-control-room-base/
Project-URL: Documentation, https://yassi.github.io/dj-control-room-base/
Project-URL: Repository, https://github.com/yassi/dj-control-room-base
Project-URL: Bug Tracker, https://github.com/yassi/dj-control-room-base/issues
Keywords: django,admin,panel
Classifier: Framework :: Django
Classifier: Framework :: Django :: 4.2
Classifier: Framework :: Django :: 5.0
Classifier: Framework :: Django :: 5.1
Classifier: Framework :: Django :: 5.2
Classifier: Framework :: Django :: 6.0
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Operating System :: OS Independent
Classifier: Intended Audience :: Developers
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: Django>=4.2
Provides-Extra: dev
Requires-Dist: pytest>=7.0.0; extra == "dev"
Requires-Dist: pytest-django>=4.5.0; extra == "dev"
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
Requires-Dist: pytest-xdist>=3.2.0; extra == "dev"
Requires-Dist: django-redis>=5.0.0; extra == "dev"
Requires-Dist: psycopg2-binary>=2.9.0; extra == "dev"
Requires-Dist: mkdocs-material>=9.1.12; extra == "dev"
Provides-Extra: build
Requires-Dist: build>=1.0.0; extra == "build"
Requires-Dist: twine>=4.0.0; extra == "build"
Dynamic: license-file

[![Django Control Room Panel](https://img.shields.io/badge/Django%20Control%20Room-Panel-0c4b33?logo=django)](https://github.com/yassi/dj-control-room)
[![Tests](https://github.com/yassi/dj-control-room-base/actions/workflows/test.yml/badge.svg)](https://github.com/yassi/dj-control-room-base/actions/workflows/test.yml)
[![codecov](https://codecov.io/gh/yassi/dj-control-room-base/branch/main/graph/badge.svg)](https://codecov.io/gh/yassi/dj-control-room-base)
[![PyPI version](https://badge.fury.io/py/dj-control-room-base.svg)](https://badge.fury.io/py/dj-control-room-base)
[![Python versions](https://img.shields.io/pypi/pyversions/dj-control-room-base.svg)](https://pypi.org/project/dj-control-room-base/)
[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT)

# dj-control-room-base

![dj-control-room-base - a core library for creating DCR panels](https://raw.githubusercontent.com/yassi/dj-control-room-base/main/images/dj-control-room-base.png)



**dj-control-room-base** is a core library for [Django Control Room](https://github.com/yassi/dj-control-room) panels. It provides the shared primitives that every panel needs: settings management, CSS injection, permission enforcement, admin sidebar integration, and template context helpers.

**Official Django Control Room panels** ship with this package as a dependency and build on these APIs rather than reimplementing them panel by panel.

**Optionally**, the package can also be mounted as a full panel in its own right: it ships a bundled design system reference UI and example patterns that are useful when building or theming new panels.

- **Official site:** [djangocontrolroom.com](https://djangocontrolroom.com)
- **Control Room app:** [dj-control-room](https://github.com/yassi/dj-control-room)
- **Docs:** [yassi.github.io/dj-control-room-base](https://yassi.github.io/dj-control-room-base/)

## What this library provides

### Centralized CSS and permissions

The main value of this library is that it centralizes the settings every panel would otherwise duplicate independently. A single `PanelConfig` object, instantiated once in a panel's `conf.py`, handles:

- **CSS injection** - whether to load the shared design-system bundle, and any additional stylesheets, resolved and injected into template context automatically
- **Permission enforcement** - staff checks, optional superuser-only restriction, Django group allow lists, and per-view scope overrides, all driven from the same merged settings dict
- **Settings merging** - panel-level defaults, hub overrides from `dj-control-room`, and project-level settings are merged in a defined precedence order so each layer can override only what it needs

Panel authors who use `PanelConfig` get all of this for free. See [CSS and permissions](#css-and-permissions) below for the full reference.

### Admin sidebar integration

`PanelPlaceholderModel` and `BasePanelAdmin` give any panel a Django admin sidebar entry that redirects to the panel's main view, with no writable actions, no migrations, and automatic respect for the same permission rules configured on `PanelConfig`.

### Template context helpers

`panel_config.get_context(request, title="...")` returns a fully-prepared context dict that includes the standard Django admin context, CSS injection variables, and any extra kwargs you pass. No manual assembly required.

### Entry-point discovery

Panels register themselves with Control Room via a `pyproject.toml` entry point under `dj_control_room.panels`. This library ships the reference implementation of that pattern.

The only runtime dependency is Django. `dj-control-room` is optional and only needed for the centralized hub dashboard.

## Screenshots

**Django admin** - the placeholder model registers an app entry that redirects to the panel, with no extra migrations required:

![Django admin home showing the DJ Control Room Base sidebar entry](https://raw.githubusercontent.com/yassi/dj-control-room-base/main/images/admin_home.png)

**The panel UI** - the bundled design system reference view, accessible at `/admin/dj-control-room-base/`:

![DJ Control Room Base design system panel view](https://raw.githubusercontent.com/yassi/dj-control-room-base/main/images/dcr-base-design-system.png)

## Requirements

- Python 3.9+
- Django 4.2+ (tested in CI across Django 4.2, 5.2, and 6.0)


## Project layout

```
dj-control-room-base/
├── dj_control_room_base/
│   ├── core/              # PanelConfig, BasePanelAdmin, PanelPlaceholderModel
│   ├── templates/         # Panel templates
│   ├── static/            # Design system CSS and assets
│   ├── conf.py            # PanelConfig instance + settings key
│   ├── panel.py           # Control Room entry-point panel class
│   ├── views.py
│   ├── urls.py
│   ├── admin.py
│   └── models.py          # Placeholder model for admin sidebar
├── example_project/       # Runnable demo + pytest settings
├── tests/
├── mkdocs.yml             # Documentation site
└── requirements.txt       # Dev / demo deps (includes dj-control-room)
```


## Quick start

```bash
pip install dj-control-room-base
```

```python
# settings.py
INSTALLED_APPS = [
    ...
    "dj_control_room_base",
]
```

```python
# urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path("admin/dj-control-room-base/", include("dj_control_room_base.urls")),
    path("admin/", admin.site.urls),
]
```

```bash
python manage.py migrate
python manage.py runserver
```

Open `/admin/` and sign in. A **DJ CONTROL ROOM BASE** entry appears in the sidebar and links to the panel at `/admin/dj-control-room-base/`.

See the [full documentation](https://yassi.github.io/dj-control-room-base/) for installation options, configuration reference, and a guide for building your own panel on this library.

## Django Control Room dashboard

1. Add `dj_control_room` to `INSTALLED_APPS` (with `dj_control_room_base`).
2. Include `path("admin/dj-control-room/", include("dj_control_room.urls"))` as above.
3. Open `/admin/dj-control-room/` to see registered panels (this package advertises itself via the `dj_control_room.panels` entry point).

Panel metadata (name, description, icon, docs/PyPI links) lives in `dj_control_room_base/panel.py`; customize a fork or your own panel package the same way.


## CSS and permissions

`PanelConfig` is the central object that every panel built on this library uses. Instantiate it once in `conf.py` with a settings key and panel-level defaults, then call its helpers from views, templates, and the admin class. The same config object drives CSS injection, permission checks, and the full template context.

### Settings merge order

Settings are resolved fresh on every call so that runtime changes (e.g. from the Control Room hub) are always picked up. The merge order is, from lowest to highest priority:

1. **Built-in defaults** (`PANEL_BUILTIN_DEFAULTS` in `core/panel_config.py`) - CSS and permission keys that every panel gets automatically, even if the panel author never declares them.
2. **Panel defaults** - the `defaults` dict passed to `PanelConfig(...)` in the panel's own `conf.py`.
3. **Hub overrides** - injected at runtime by `dj-control-room` via `apply_override_settings()` when the hub is installed. Lets the hub enforce global CSS or permission policies across all panels from a single place.
4. **Project settings** - the dict at `settings.DJ_<PANEL>_SETTINGS` in the consuming Django project. These win over everything, so a project can always opt out of hub defaults.

The result is that panel authors declare a minimal `defaults`, project owners override only what they need, and the hub can push cross-panel policy without touching individual panel settings files.

### CSS settings

| Key | Type | Default | Effect |
|---|---|---|---|
| `LOAD_DEFAULT_CSS` | `bool` | `True` | Loads the shared `design-system.css` bundle from this package. Set to `False` if the hub or a parent template already loads it. |
| `EXTRA_CSS` | `list[str]` | `[]` | Additional stylesheets to inject. Relative paths are resolved through Django's `staticfiles`; absolute URLs (`http://`, `https://`, `//`) are used as-is. |

In a view, call `panel_config.get_context(request, title="My Panel")` to get a context dict that already contains `dj_cr_load_default_css` and `dj_cr_extra_css` alongside the standard Django admin context. Templates use these to inject the right `<link>` tags without any per-view wiring.

### Permission settings

Panels built on this library follow a two-level permission model: a **panel-wide** policy, and optional **per-scope** overrides for individual views.

**Panel-wide keys** (apply to all views unless a scope overrides them):

| Key | Type | Default | Effect |
|---|---|---|---|
| `ALLOWED_GROUPS` | `list[str]` | `[]` | Django group names that may access the panel. Empty list means any staff user is allowed. |
| `REQUIRE_SUPERUSER` | `bool` | `False` | Restricts the panel to superusers only. |

**Per-view scopes** (`SCOPE_PERMISSIONS`):

A scope is a string label that matches the argument passed to `@panel_config.permission_required("my-scope")` on a view. Each scope entry accepts the same `ALLOWED_GROUPS` and `REQUIRE_SUPERUSER` keys and overrides the panel-wide values for that view only.

```python
"SCOPE_PERMISSIONS": {
    "reports": {"REQUIRE_SUPERUSER": True},     # only superusers
    "dashboard": {"ALLOWED_GROUPS": ["ops"]},   # ops group only
    "status": {},                               # inherits panel-wide policy
}
```

**Resolution rules** (applied in order):

1. The user must be **authenticated** and **staff**. Anonymous users are redirected to the Django admin login.
2. **Superusers** always pass (they bypass group checks, matching Django admin behaviour).
3. If `REQUIRE_SUPERUSER` is `True` for the resolved scope, non-superusers receive a 403.
4. If `ALLOWED_GROUPS` is non-empty, the user must belong to at least one of those groups (checked by name). An empty list skips the group check.

### Using it in a panel

```python
# myapp/conf.py
from dj_control_room_base.core import PanelConfig

panel_config = PanelConfig(
    settings_key="DJ_MY_PANEL_SETTINGS",
    defaults={
        "LOAD_DEFAULT_CSS": True,
        "EXTRA_CSS": [],
    },
)

# myapp/views.py
from .conf import panel_config

@panel_config.permission_required("dashboard")
def dashboard(request):
    context = panel_config.get_context(request, title="Dashboard")
    return render(request, "mypanel/dashboard.html", context)
```

That is the entirety of the wiring. Permission enforcement, login redirect, CSS injection, and the Django admin context are all handled by the two `panel_config` calls.


## Building your own panel on this package

Import primitives from `dj_control_room_base.core`:

- **`PanelConfig`** - Instantiate in `conf.py` with your settings key and defaults; use `get_context`, `permission_required`, and CSS helpers in views.
- **`PanelPlaceholderModel`** - Abstract `managed=False` base for a sidebar-only model.
- **`BasePanelAdmin`** - Redirect changelist to your `namespace:index` URL; set `panel_config` for aligned permissions.

Copy the entry-point pattern from `pyproject.toml`:

```toml
[project.entry-points."dj_control_room.panels"]
my_panel = "my_panel.panel:MyPanel"
```


## Development

Clone and install in editable mode with dev dependencies:

```bash
git clone https://github.com/yassi/dj-control-room-base.git
cd dj-control-room-base
python -m venv .venv
source .venv/bin/activate   # Windows: .venv\Scripts\activate
make install                # pip install -r requirements.txt && pip install -e .
```

Run tests locally (uses SQLite by default via `example_project` settings):

```bash
make test_local
# or: python -m pytest tests/ -v
```

Coverage:

```bash
make test_coverage
```

Docker Compose provides a **dev** container (app on port 8000) and optional **Postgres**. From the repo root:

```bash
make docker_up
make docker_shell    # working directory: /app/example_project
```

Inside the container you can run `python manage.py runserver 0.0.0.0:8000` or pytest. For Postgres-backed runs, set `DB_ENGINE=postgresql` and point host/user/password at the `postgres` service.

Documentation:

```bash
make docs          # mkdocs build
make docs_serve    # local preview
```


## License

MIT. See [LICENSE](LICENSE).
