Metadata-Version: 2.4
Name: bigb-django
Version: 1.1.0.dev0
Summary: Dashboards configurables pour Django : requêtes JSON sur les modèles, rendu JSON/SVG, dashboards en grille partageables par utilisateur ou groupe
License: LGPL-3.0-or-later
Project-URL: Homepage, https://git.stationmyr.net/Gilles/bigb-django
Classifier: Framework :: Django
Classifier: Framework :: Django :: 4.2
Classifier: Framework :: Django :: 5.0
Classifier: Framework :: Django :: 5.1
Classifier: Framework :: Django :: 5.2
Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)
Classifier: Programming Language :: Python :: 3
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: COPYING
License-File: COPYING.LESSER
Requires-Dist: Django>=4.2
Requires-Dist: pygal>=3.0
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == "dev"
Requires-Dist: pytest-django; extra == "dev"
Requires-Dist: pytest-cov; extra == "dev"
Dynamic: license-file

# bigb-django

[![CI](https://git.stationmyr.net/Gilles/bigb-django/badges/workflows/ci.yml/badge.svg?branch=master)](https://git.stationmyr.net/Gilles/bigb-django/actions?workflow=ci.yml)
[![Coverage](https://reports.stationmyr.net/Gilles/bigb-django/branch/master/badge/cobertura.svg?aggregate=1)](https://reports.stationmyr.net/Gilles/bigb-django/branch/master/cobertura)
[![Tests](https://reports.stationmyr.net/Gilles/bigb-django/branch/master/badge/junit.svg?aggregate=1)](https://reports.stationmyr.net/Gilles/bigb-django/branch/master/junit)
[![License: LGPL v3](https://img.shields.io/badge/license-LGPLv3-blue.svg)](COPYING.LESSER)

Configurable dashboards for Django: declarative JSON queries against a
Django project's models (explicitly whitelisted, no implicit access),
JSON or SVG rendering (via [pygal](https://www.pygal.org/)), named saved
queries, and grid dashboards shareable by user or group. The Vue 3
frontend counterpart is
[bigb-vue](https://git.stationmyr.net/Gilles/bigb-vue).

## Installation

```bash
pip install bigb-django
```

```python
# settings.py
INSTALLED_APPS = [
    ...,
    "django.contrib.auth",
    "bigb",
]

BIGB_MAX_LIMIT = 1000  # cap applied to `limit` in a QuerySpec
```

```python
# urls.py
urlpatterns = [
    ...,
    path("bigb/", include("bigb.urls")),
]
```

Don't forget `python manage.py migrate` (`SavedQuery`, `Dashboard`,
`DashboardTile` models).

## Declaring queryable models

Nothing is queryable by default. Each app explicitly declares what it
exposes, in a `bigb_queries.py` module (auto-discovered at startup, the
same way `admin.py` is for the Django admin):

```python
# myapp/bigb_queries.py
from bigb import registry
from .models import Order

registry.register(
    Order,
    fields=["id", "status", "amount", "created_at"],
    filterable=["status", "created_at"],
    aggregatable=["amount"],
    # Optional: called back on EVERY execution (not just when the query
    # is saved), so it protects already-saved queries too.
    scope=lambda qs, user: qs.filter(tenant=user.tenant),
)
```

## QuerySpec (JSON)

```json
{
  "model": "orders.Order",
  "filters": [{"field": "status", "op": "eq", "value": "paid"}],
  "group_by": ["status"],
  "aggregates": [{"func": "sum", "field": "amount", "as": "total"}],
  "order_by": ["-total"],
  "limit": 100
}
```

- `fields` (optional, no `group_by`/`aggregates`): the columns returned,
  in that order -- e.g. `["created_at", "amount"]` for a line chart.
  Absent: every registered field.
- `op`: `eq`, `neq`, `gt`, `gte`, `lt`, `lte`, `in`, `contains`, `isnull`.
- `func`: `count`, `sum`, `avg`, `min`, `max`. `{"func": "count"}` without
  `field` counts all rows (`count_all`).
- Every field referenced must be in the model's whitelist in the
  registry (`fields`/`filterable`/`aggregatable`), otherwise a 400.

## API

All routes require an authenticated session.

| Method | Route | Description |
|---|---|---|
| POST | `/bigb/query/` | Run an ad hoc QuerySpec (`execute_query`) |
| GET/POST | `/bigb/queries/` | List / create a saved query |
| GET/PATCH/DELETE | `/bigb/queries/<slug>/` | Detail / update / delete |
| GET | `/bigb/queries/<slug>/data/?format=json\|svg&chart=line` | Result (`view_savedquery_results`) |
| GET/POST | `/bigb/dashboards/` | List / create a dashboard |
| GET | `/bigb/dashboards/default/` | The current user's default dashboard -- their own `is_default` one, or else the first `is_default` one shared with one of their groups. 404 if neither exists |
| GET/PATCH/DELETE | `/bigb/dashboards/<slug>/` | Detail (with tiles) / update / delete |
| POST | `/bigb/dashboards/<slug>/tiles/` | Add a tile |
| PATCH/DELETE | `/bigb/dashboards/<slug>/tiles/<id>/` | Update / delete a tile |
| GET | `/bigb/dashboards/<slug>/tiles/<id>/data/?format=json\|svg` | Tile result |
| GET | `/bigb/registry/` | Available models/fields/aggregates |

`format=svg` accepts `chart=line\|bar\|pie`, `title=...`, `label_field=...`.

## Permissions

Standard Django permissions, assignable per user or per group (so
manageable from the Django admin). Each resource has a base permission,
scoped to "my objects, or ones shared with my user/my groups"
(`shared_users`/`shared_groups`), and an `*_all` permission that lifts
that scoping:

| Permission | Effect |
|---|---|
| `view_savedquery` | View queries you own or are a sharing recipient of |
| `view_all_savedquery` | View everyone's queries |
| `view_savedquery_results` | Run/view the data of an already-visible query |
| `execute_query` | Run an ad hoc (unsaved) query |
| `add_savedquery` | Create a query |
| `change_savedquery` / `change_all_savedquery` | Edit your own queries / everyone's |
| `delete_savedquery` / `delete_all_savedquery` | Delete your own queries / everyone's |
| `view_dashboard` / `view_all_dashboard` | View your dashboards (+ shared) / everyone's |
| `add_dashboard` | Create a dashboard |
| `change_dashboard` / `change_all_dashboard` | Edit your own dashboards / everyone's |
| `delete_dashboard` / `delete_all_dashboard` | Delete your own dashboards / everyone's |
| `manage_group_dashboard` | Create a dashboard shared with a group, or change/delete one that already is -- a separate gate from `change_dashboard`/`change_all_dashboard`, which only ever lift the *ownership* scoping. Without it, sharing a dashboard with a group (or editing one someone else already shared with a group) is refused even for its own owner |

A `Dashboard.is_default` flag marks the one to show automatically for
its owner (or, for a group-shared dashboard, for members of that group
who have no personal default of their own) -- see `/bigb/dashboards/
default/` above. Setting it follows the same rules as any other field:
no extra permission for your own dashboard, `manage_group_dashboard`
for a group-shared one.

### Per-query extra permission

`SavedQuery.required_permission` (blank by default) names one extra
Django permission (`app_label.codename`) a viewer must additionally
hold to see that *specific* query's results -- on top of, never
instead of, the usual `view_savedquery_results` + shared_with check.
Useful when a query is shared with a group but its data should stay
reserved to whoever also holds some more specific permission (e.g. a
host app scoping a sensitive aggregate to its own "staff" permission,
while a blander one in the same dashboard stays open to the whole
group). It only ever gates results: the query's own definition stays
visible to anyone the normal `view_savedquery`/shared_with rules
already let see it (so it can still be listed, just not run), and a
blank value changes nothing, which is the default for every existing
query. Settable via `POST /bigb/queries/`/`PATCH /bigb/queries/<slug>/`
like any other field.

## Development

```bash
make dev    # venv + editable install + [dev] extras
make test   # pytest
make lint   # pylint (pylint-django)
make format # autopep8 --in-place
make bandit # static security analysis
make tox    # every environment (py311-py315 + lint + format + bandit)
```
