# django-grid-view — LLM context bundle

> Auto-generated by `scripts/build_llm_context.py`. Do not edit by hand.
> Regenerate: `uv run python scripts/build_llm_context.py`

Package: **django-grid-view** (PyPI) · Module: **django_grid_view** · Docs: https://alpiua.github.io/django-grid-view/

---



<!-- source: index.md -->

# Django Grid View

**django-grid-view** is a reusable Django app for declarative data views in dashboards and chat UIs.

Install from PyPI:

```bash
pip install django-grid-view
```

## Three ways to render data

### Simple Table

Server-rendered lists with sort, search, and export.

- **Python:** `SimpleTableConfig`
- **Template:** `{% render_simple_table %}`

### AG-Grid

Large datasets, infinite row model, saved searches.

- **Python:** your view + `GridPreference`
- **Template:** `{% django_grid_view_scripts %}`

### Grid View 1.0

KPI strip, ECharts, and table from one spec.

- **Python:** `GridRenderer` / `build_artifact_from_view`
- **Template:** `{% render_grid_view %}`

![Layer model: rows + GridViewSpec → GridArtifact](assets/layer-model.svg)

**Core rule:** numeric KPI and chart values always come from Python `rows`. Specs and LLM output describe structure only.

## Quick links

- Getting started (see section: getting-started.md) — install, Django setup, first table
- Grid View artifacts (see section: grid-view-artifacts.md) — `GridViewSpec` → `GridArtifact`
- Changelog (see section: changelog.md) — release notes
- LLM context bundle (see section: llm/django-grid-view-llm-context.md) — single file for agents (about the bundle (see section: llm/context.md))
- GridViewSpec reference (see section: reference/grid-view-spec.md) — JSON Schema contract
- Python types (see section: reference/python-types.md) — imports for host apps (pyright/mypy)

## Publishing this documentation

The docs site is built with [MkDocs Material](https://squidfunk.github.io/mkdocs-material/) and deployed to GitHub Pages via the `Docs` workflow. Repository maintainers: enable **Settings → Pages → Build and deployment → GitHub Actions**, then set the repository **Website** to `https://alpiua.github.io/django-grid-view/`.

## Links

- [PyPI](https://pypi.org/project/django-grid-view/)
- [GitHub](https://github.com/alpiua/django-grid-view)
- [Issue tracker](https://github.com/alpiua/django-grid-view/issues)


---


<!-- source: getting-started.md -->

# Getting started

## Install

```bash
pip install django-grid-view
```

Editable install for local development:

```bash
pip install -e /path/to/django-grid-view
```

With **uv**, override PyPI in your project:

```toml
[project]
dependencies = ["django-grid-view>=1.0.0"]

[tool.uv.sources]
django-grid-view = { path = "../django-grid-view", editable = true }
```

## Django setup

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

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

urlpatterns = [
    path("", include("django_grid_view.urls")),
]
```

```bash
python manage.py migrate django_grid_view
```

This creates the `GridPreference` model used for per-user column presets and saved searches (optional; required if you use the save API).

## Load assets once per page

Grid View inclusion tags auto-load CSS/JS on first use. For explicit control:

```django
{% load django_grid_view %}
{% grid_view_bundle %}
```

Place `{% grid_view_bundle %}` in your base template, or rely on auto-load from `render_simple_table`, `render_grid_view`, etc.

## First Simple Table

**View** — build rows in Python:

```python
from django.shortcuts import render
from django_grid_view.tables import Column, SimpleTableConfig

def doctors_list(request):
    rows = [
        {"name": "Ada", "visits": 12},
        {"name": "Bob", "visits": 8},
    ]
    config = SimpleTableConfig(
        grid_id="doctors",
        columns=[
            Column(key="name", label="Doctor"),
            Column(key="visits", label="Visits", align="right"),
        ],
        data=rows,
    )
    return render(request, "doctors.html", {"table": config})
```

**Template:**

```django
{% load django_grid_view %}
{% render_simple_table table %}
```

Open the page — client-side sort and search work without extra JavaScript.

## Typing in host projects

The package includes **`py.typed`**. Import contracts instead of copying TypedDicts:

```python
from django_grid_view.types import GridViewSpec, GridViewSpecWire, JsonObject, RowDict
from django_grid_view.tables import Column, SimpleTableConfig
from django_grid_view.render import build_artifact_from_view, parse_grid_view_spec
```

See Python types (see section: reference/python-types.md) for wire types, `GridArtifactJson`, enums, and pyright setup.

## Next steps

- Simple Table (see section: simple-table.md) — columns, export, footers
- AG-Grid integration (see section: ag-grid.md) — HTMX-safe grids and preferences
- Charts and KPIs (see section: charts-and-kpis.md) — ECharts and KPI strips
- Grid View artifacts (see section: grid-view-artifacts.md) — unified `GridViewSpec` rendering


---


<!-- source: simple-table.md -->

# Simple Table

Server-rendered HTML tables with client-side sort, search, and optional export — no AG-Grid required.

## Configuration

```python
from django_grid_view.tables import Column, ColumnGroup, SimpleTableConfig
from django_grid_view.types import RowDict

rows: list[RowDict] = [{"sku": "A1", "name": "Widget", "price": 9.99}]

config = SimpleTableConfig(
    grid_id="products",
    columns=[
        Column(key="sku", label="SKU", width="120px"),
        Column(key="name", label="Name"),
        Column(key="price", label="Price", align="right"),
    ],
    data=rows,
    search_mode="global",  # global | per_column | disabled
    striped=True,
    export_xlsx=True,
)
```

### Column options

| Field | Purpose |
|-------|---------|
| `key` | Row dict key |
| `label` | Header text |
| `sortable` | Enable column sort (default `True`) |
| `searchable` | Include in client search (default `True`) |
| `align` | `left`, `center`, `right` |
| `sort_value` | Callable for custom sort key |
| `export_raw` | Callable for XLSX raw value |
| `render` | Override cell HTML (subclass `Column`) |

### Column groups

Multi-level headers:

```python
ColumnGroup(label="Q1", column_keys=["jan", "feb", "mar"])
```

### Row actions

```python
SimpleTableConfig(
    grid_id="orders",
    columns=[...],
    data=rows,
    row_url="/orders/{id}/",  # placeholders from row keys
    # or row_onclick="openOrder({id})"
)
```

### Footer row

```python
footer_row={"name": "Total", "amount": 1000},
footer_label="Summary",
footer_label_span=2,
```

## Template tag

```django
{% load django_grid_view %}
{% render_simple_table config %}
```

The tag builds header/footer rows and injects `data-cm-*` attributes consumed by `grid-view.js` (`CmSimpleTable` / `GridView.SimpleTable`).

## Export

Enable `export_xlsx=True` on `SimpleTableConfig`. The bundle includes client-side XLSX export for the active table (tab-aware when using tab panes).

For PDF, pass `export_pdf_url` to a server endpoint that returns a file download.

## When to use Simple Table vs Grid View 1.0

| Use Simple Table | Use Grid View artifact |
|------------------|------------------------|
| Custom `Column.render()` HTML | Declarative `GridViewSpec` + KPI + charts |
| Legacy dashboard tables | Chat / LLM-driven views |
| No KPI/chart on same block | One `{% render_grid_view %}` block |

See Grid View artifacts (see section: grid-view-artifacts.md) for the unified spec path.


---


<!-- source: ag-grid.md -->

# AG-Grid integration

django-grid-view provides HTMX-safe helpers, toolbar partials, and a save API for column presets and searches. **You own the AG-Grid instance** — the package does not create `gridApi` for you.

## Load CDN scripts outside HTMX swaps

Put AG Grid and Sortable in your **base template**, not inside fragments replaced by HTMX:

```html
<script src="https://cdn.jsdelivr.net/npm/ag-grid-community@31.3.2/dist/ag-grid-community.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/sortablejs@latest/Sortable.min.js"></script>
```

Also load the django-grid-view bundle (once per page):

```django
{% load django_grid_view %}
{% grid_view_bundle %}
```

## Minimal grid page

```html
<div id="products-grid" class="ag-theme-quartz-dark"></div>

<script>
  window.ProductsGrid = {
    columnDefs: ProductColumns,
    rowModelType: "infinite",
    cacheBlockSize: 200,
    context: { gridId: "products" },
  };
</script>

{% django_grid_view_scripts grid_id="products" options_var="ProductsGrid" container_id="products-grid" %}
```

`django_grid_view_scripts` initializes the grid, wires search/modal UI, and persists settings when the user is logged in.

## Partials

Compose toolbar, search, and modal separately:

```django
{% render_django_grid_view_toolbar grid_id="products" %}
{% render_django_grid_view_search grid_id="products" %}
{% render_django_grid_view_modal grid_id="products" %}
```

Or use the combined toolbar + modal tag. Optional plugins:

```django
{% include "django_grid_view/plugins/smart_filter.html" %}
{% include "django_grid_view/plugins/advanced_search.html" %}
{% include "django_grid_view/plugins/custom_tooltip.html" %}
```

## Runtime rules

1. **Never** load AG Grid CDN scripts inside HTMX-swapped content — scripts would re-run and break grid state.
2. For **infinite row model**, do not use `quickFilterText` or `autoHeight`. Use the package search integration (`manager._searchText`) and filter on the server.
3. Define `columnDefs` (or your `options_var` object) **before** `django_grid_view_scripts` runs.

## KPI and charts on filtered grid rows

Python renders unresolved KPI specs; the browser aggregates visible rows:

```django
{% render_grid_kpi_strip kpi_specs columns=4 %}
```

```javascript
var adapter = GridView.createAgGridAdapter(gridApi);
GridView.bindGridKpis({ gridAdapter: adapter });
```

Charts with `data_source: "grid_filtered"` update on filter/sort via `GridView.bindGridFilteredCharts`. See JavaScript API (see section: reference/javascript.md) and Charts and KPIs (see section: charts-and-kpis.md).

## Saved preferences

Per-user column presets and searches use `GridPreference` and `POST /api/django-grid-view/save/`. See Saved preferences (see section: preferences.md).


---


<!-- source: charts-and-kpis.md -->

# Charts and KPIs

Grid View 1.0 resolves KPI values and chart bindings in Python from `rows`, then renders ECharts in the browser.

## KPI strip (Python-resolved)

```python
from django_grid_view.types import ColumnFormat, KpiAggregate, KpiSpec, RowDict
from django_grid_view.render.kpi import resolve_kpis

specs = [
    KpiSpec(
        label="Total",
        column_key="amount",
        aggregate=KpiAggregate.SUM,
        format=ColumnFormat.NUMBER,
    ),
]
kpis = resolve_kpis(specs, rows)
```

```django
{% render_kpi_strip kpis columns=4 %}
```

## Charts (static rows)

Requires `window.echarts` on the page (load ECharts CDN in your base template).

```python
from django_grid_view.types import ChartSpec, ChartType, RowDict, SeriesSpec
from django_grid_view.render.charts import build_chart_runtime

chart = ChartSpec(
    id="main",
    chart_type=ChartType.BAR,
    x_key="name",
    series=(SeriesSpec(key="amount", label="Amount"),),
)
runtime = build_chart_runtime(chart, rows)
```

```django
{% render_chart chart rows %}
```

## AG-Grid–filtered KPIs and charts

When KPIs or charts must reflect **visible** AG-Grid rows after filter/sort:

- Python: `{% render_grid_kpi_strip specs %}` (unresolved specs)
- JS: `GridView.createAgGridAdapter(gridApi)` + `bindGridKpis` / `bindGridFilteredCharts`

`ChartSpec.data_source` can be `static` (artifact rows) or `grid_filtered` (adapter rows).

## Unified layout

Prefer one artifact when KPI, chart, and table share the same `rows`:

```python
from django_grid_view.types import GridViewSpec, RowDict
from django_grid_view.render import GridRenderer

artifact = GridRenderer.build(spec, rows)
```

Types: Python types (see section: reference/python-types.md).

```django
{% render_grid_view artifact %}
```

See Grid View artifacts (see section: grid-view-artifacts.md).


---


<!-- source: grid-view-artifacts.md -->

# Grid View artifacts

`GridViewSpec + rows → GridArtifact` is the core render contract for unified KPI, chart, and table blocks.

## Quick start

```python
from django_grid_view.types import JsonObject, RowDict
from django_grid_view.render import build_artifact_from_view

rows: list[RowDict] = [{"name": "A", "amount": 10}, {"name": "B", "amount": 20}]
view: JsonObject = {
    "grid_id": "demo",
    "title": "Demo",
    "columns": [
        {"key": "name", "label": "Name", "format": "text"},
        {"key": "amount", "label": "Amount", "format": "number"},
    ],
    "kpis": [
        {"label": "Total", "column_key": "amount", "aggregate": "sum", "format": "number"}
    ],
    "charts": [{
        "id": "main",
        "chart_type": "bar",
        "x_key": "name",
        "series": [{"key": "amount", "label": "Amount"}],
    }],
    "layout": {"blocks": ["title", "kpis", "chart", "table"]},
}

artifact = build_artifact_from_view(view, rows)
payload = artifact.to_json()  # wire shape for GridView.init
```

Typed builders (recommended for dashboards):

```python
from django_grid_view.types import (
    ChartSpec,
    ChartType,
    ColumnSpec,
    GridViewSpec,
    KpiSpec,
    RowDict,
    SeriesSpec,
)
from django_grid_view.render import GridRenderer

spec = GridViewSpec(
    grid_id="demo",
    columns=(ColumnSpec(key="name", label="Name"),),
    kpis=(),
    charts=(
        ChartSpec(
            id="main",
            chart_type=ChartType.BAR,
            x_key="name",
            series=(SeriesSpec(key="amount", label="Amount"),),
        ),
    ),
)
artifact = GridRenderer.build(spec, rows)
```

Import map: Python types (see section: reference/python-types.md).

## API

| Function | Input | Output |
|----------|-------|--------|
| `parse_grid_view_spec_json(raw)` | dict | `GridViewSpec` |
| `GridRenderer.build(spec, rows)` | validated spec + rows | `GridArtifact` |
| `build_artifact_from_view(view, rows)` | raw dict or spec + rows | `GridArtifact` |
| `build_artifact_json_from_view(view, rows)` | raw dict or spec + rows | `GridArtifactJson` |

Use `build_artifact_from_view` when the spec comes from JSON (LLM or Python dict).
Use `GridRenderer.build` when you already have a validated `GridViewSpec`.

## Invariants

- KPI and chart **numbers** are computed from `rows` inside `GridRenderer.build`.
- The spec must not contain row data or numeric KPI values.
- Schema: GridViewSpec reference (see section: reference/grid-view-spec.md) and `schema/grid-view-spec.v1.json` in the repository.

## Template rendering

```django
{% load django_grid_view %}
{% render_grid_view artifact %}
```

## Client-side init (SPA / chat)

```javascript
GridView.init({ root: document.getElementById("chat-panel"), artifact: payload });
```

## Consumer integration

Router state lookup, chat SSE wrapping, and visualizer prompts are **not** part of this package. See Chat visualizer (see section: guides/chat-visualizer.md) for a generic wire contract.


---


<!-- source: preferences.md -->

# Saved preferences

django-grid-view stores per-user AG-Grid column presets and saved searches in the `GridPreference` model.

## Model

| Field | Type | Purpose |
|-------|------|---------|
| `user` | FK to `AUTH_USER_MODEL` | Owner |
| `grid_id` | `CharField(100)` | Matches `context.gridId` in grid options |
| `col_presets` | JSON object | Named column layouts |
| `searches` | JSON list | Saved search strings |

Unique together: `(user, grid_id)`.

## Setup

Include app URLs and run migrations (see Getting started (see section: getting-started.md)):

```bash
python manage.py migrate django_grid_view
```

## Save API

**Endpoint:** `POST /api/django-grid-view/save/`

**Auth:** `login_required` — anonymous users cannot save.

**Body (JSON):**

```json
{
  "grid_id": "products",
  "colPresets": { "default": { "columnState": [] } },
  "searches": ["status:active", "category:books"]
}
```

**Response:** `{"status": "ok"}` or `{"status": "error", "message": "..."}` with HTTP 400.

The bundled `django_grid_view_scripts` tag calls this endpoint when users change column layout or save searches (grid `context.gridId` must match `grid_id`).

## Security notes

- Only authenticated users can write preferences; reads are scoped to `request.user` in the grid manager.
- Validate `grid_id` in your views if you expose grids with sensitive data — the package does not enforce row-level security on saved JSON.


---


<!-- source: architecture.md -->

# Architecture

This document summarizes how **django-grid-view** is structured.

## Purpose

`django-grid-view` provides reusable **grid view** rendering for Django applications:

1. **Simple Table** — server-rendered HTML tables (sort, search, export)
2. **AG-Grid** — HTMX-safe lifecycle, preferences, infinite row model helpers
3. **Grid View** (1.0) — unified KPI cards, ECharts charts, and tabular data

![Layer model](assets/layer-model.svg)

## Layer model

```
Data (Python)          Spec (declarative)        Render (adapters)
─────────────────────────────────────────────────────────────────
rows: list[dict]   +   GridViewSpec          →   GridArtifact
                                               ├─ SimpleTableConfig
                                               ├─ AgGridColumnDefs
                                               ├─ ResolvedKpi[]
                                               └─ ChartRuntimeConfig[]
```

**Rule:** Numbers always come from Python `rows`. The LLM (chat visualizer) supplies structure only.

### Ownership boundaries

| Layer | Responsibility |
|-------|----------------|
| **Package** | Types, parser, `GridRenderer`, adapters, `grid-view.js`, CSS, i18n |
| **Consumer app** | `rows` (SQL/ORM), domain labels/URLs, AG-Grid instance |
| **Chat / LLM** | `GridViewSpec` JSON only; no row data or KPI numbers |

The package **never** instantiates AG-Grid and **never** accepts numeric KPI values from the LLM.

## ChartSpec and AG-Grid

| `data_source` | Behavior |
|---------------|----------|
| `static` | Use `rows` from `GridArtifact` |
| `grid_filtered` | Visible rows from AG-Grid via `GridView.createAgGridAdapter` |

See JavaScript API (see section: reference/javascript.md).

## Front-end bundle

All browser code lives in **`static/django_grid_view/grid-view.js`** — one IIFE, no Vite build. Python types in `types/chart_bind.py` define the chart runtime contract.

Host apps import public types from `django_grid_view.types` (Python types (see section: reference/python-types.md)); `py.typed` is shipped in the wheel.

## Internationalization

Package chrome uses Django `locale/` (en + uk) and `window.GridViewI18n` injected before the bundle.

## Package modules

| Module | Responsibility |
|--------|----------------|
| `types/` | Enums, specs, JSON parsing |
| `render/` | `GridRenderer`, KPI/chart resolution |
| `tables.py` | `Column`, `SimpleTableConfig` |
| `templatetags/` | Inclusion tags |
| `models.py` | `GridPreference` |
| `static/django_grid_view/` | `grid-view.js`, `table.css` |

## Related documents

- Grid View artifacts (see section: grid-view-artifacts.md)
- GridViewSpec reference (see section: reference/grid-view-spec.md)


---


<!-- source: reference/template-tags.md -->

# Template tags

Load tags with `{% load django_grid_view %}`.

## Asset bundle

| Tag | Template | Purpose |
|-----|----------|---------|
| `{% grid_view_bundle %}` | `bundle.html` | CSS + `grid-view.js` (once per page) |

Inclusion tags below auto-load assets on first use unless `grid_view_bundle` was already rendered.

## Simple Table

| Tag | Arguments | Purpose |
|-----|-----------|---------|
| `{% render_simple_table config %}` | `SimpleTableConfig` | Full server-rendered table |

## Grid View 1.0

| Tag | Arguments | Purpose |
|-----|-----------|---------|
| `{% render_grid_view artifact %}` | `GridArtifact`, optional `interactive=True` | Unified KPI + charts + table |
| `{% render_kpi_strip kpis %}` | resolved KPIs, `columns=4` | Static KPI strip |
| `{% render_chart chart rows %}` | `ChartSpec` or runtime config, rows | ECharts block |
| `{% render_grid_kpi_strip specs %}` | `KpiSpec` sequence, `columns=4` | AG-Grid KPI (client aggregates) |

## AG-Grid helpers

| Tag | Arguments | Purpose |
|-----|-----------|---------|
| `{% django_grid_view_scripts %}` | `grid_id`, `options_var`, `container_id`, optional `toolbar`, `modal` | Init grid + wiring |
| `{% render_django_grid_view_toolbar %}` | `grid_id` (context) | Toolbar + gear |
| `{% render_django_grid_view_search %}` | `grid_id` | Search bar |
| `{% render_django_grid_view_gear %}` | `grid_id` | Gear button only |
| `{% render_django_grid_view_modal %}` | `grid_id` | Column/search modal |

### `django_grid_view_scripts` parameters

| Parameter | Required | Description |
|-----------|----------|-------------|
| `grid_id` | yes | Preference key / DOM id prefix |
| `options_var` | yes | Global JS variable name (e.g. `"ProductsGrid"`) |
| `container_id` | yes | Element id for the grid div |
| `toolbar` | no | Include toolbar partial (default true) |
| `modal` | no | Include modal partial (default true) |

## Context helper

`get_grid_state(context, grid_id)` — returns `(col_presets_json, searches_json)` for embedding in templates (used internally by toolbar).


---


<!-- source: reference/javascript.md -->

# JavaScript API

The package ships a single hand-maintained bundle: `static/django_grid_view/grid-view.js` (global `GridView` / `CmGridView`). No Node build step.

Load via `{% grid_view_bundle %}` or any inclusion tag that sets `load_assets`.

## GridView.init

```javascript
var disconnect = GridView.init({
  root: document,           // scope for queries
  artifact: artifactJson, // optional GridArtifact payload
  gridAdapter: adapter,     // optional AG-Grid adapter
});
// disconnect() — unsubscribe grid_filtered KPI/chart listeners
```

Initializes Simple Tables, static KPI strips, charts, and optionally binds AG-Grid KPI/chart refresh.

## AG-Grid adapter

```javascript
var adapter = GridView.createAgGridAdapter(gridApi);
GridView.bindGridKpis({ root: document, gridAdapter: adapter });
GridView.bindGridFilteredCharts(document, adapter);
```

| Method | Purpose |
|--------|---------|
| `getRows()` | Visible row data after filter/sort |
| `onChange(cb)` | Subscribe to model updates; returns unsubscribe |

Static rows (tests or non-grid pages):

```javascript
var adapter = GridView.staticRowsAdapter(rowsArray);
```

## KPI helpers

| API | Purpose |
|-----|---------|
| `GridView.resolveKpis(specs, rows)` | Client-side aggregate (mirrors Python) |
| `GridView.Kpi.initKpiStrip(root, kpis, columns)` | Render resolved KPI cards |
| `GridView.bindGridKpis({ gridAdapter })` | Wire `[data-cm-grid-kpi]` strips |

## Charts

| API | Purpose |
|-----|---------|
| `GridView.initChart(el, config, rows)` | Mount one ECharts instance |
| `GridView.initAllCharts(scope)` | Scan `[data-cm-chart-config]` |
| `GridView.buildEchartsOption(config, rows)` | Build option object |
| `GridView.refreshChartWrap(node, config, rows)` | Update chart data |
| `GridView.bindGridFilteredCharts(scope, adapter)` | `data_source: grid_filtered` |

Requires global `echarts`.

## Simple Table

```javascript
GridView.SimpleTable.initAll(scope);
// legacy alias: CmSimpleTable.initAll(scope)
```

## i18n

Django injects `window.GridViewI18n` before the bundle:

```javascript
GridView.i18n.t("search.placeholder", "Search…");
```

Add translations under `django_grid_view/locale/`.

## Aliases

`CmGridView` is identical to `GridView` for backward compatibility.


---


<!-- source: reference/grid-view-spec.md -->

# GridViewSpec

Declarative JSON contract for Grid View 1.0. **Rows are never embedded in the spec** — pass them separately to `GridRenderer.build` or `build_artifact_from_view`.

Canonical schema file in the repository:

```
schema/grid-view-spec.v1.json
```

## Required fields

| Field | Type | Description |
|-------|------|-------------|
| `grid_id` | string | DOM id prefix (`^[a-z][a-z0-9_-]*$`) |
| `columns` | array | At least one column spec |

## Top-level optional fields

| Field | Description |
|-------|-------------|
| `title` | Heading above the view |
| `kpis` | Up to 8 KPI specs (structure only) |
| `charts` | Up to 3 chart specs |
| `layout` | Block order: `title`, `kpis`, `chart`, `table`, … |
| `wrapper` | `full`, `inner`, or `shell` |
| `search_mode` | `global`, `per_column`, `disabled` |
| `striped` | Table zebra striping |
| `export_xlsx` | Enable client XLSX export |
| `export_pdf_url` | Server PDF download URL |

## Column spec

| Field | Default | Notes |
|-------|---------|-------|
| `key`, `label` | required | Row dict key and header |
| `format` | `text` | `text`, `number`, `currency`, `percent` |
| `align` | `left` | `left`, `center`, `right` |
| `sortable`, `searchable` | `true` | |
| `link_template` | — | URL with `{placeholders}` |
| `width` | — | CSS width |

## KPI spec

| Field | Notes |
|-------|-------|
| `label` | required |
| `column_key` | Column to aggregate (optional for `count`) |
| `aggregate` | `count`, `sum`, `avg`, `min`, `max`, … |
| `format` | Display format enum |
| `icon`, `tone` | Optional card styling |

## Chart spec

| Field | Notes |
|-------|-------|
| `id` | Unique within view |
| `chart_type` | `bar`, `line`, `pie`, … |
| `x_key` | Category axis field |
| `series` | `[{ "key", "label" }]` |
| `data_source` | `static` or `grid_filtered` |
| `height` | Pixel height |

## Python usage

```python
from django_grid_view.types import GridViewSpecWire, JsonObject, RowDict
from django_grid_view.render import GridRenderer, parse_grid_view_spec, parse_grid_view_spec_json

rows: list[RowDict] = [...]

# JSON from LLM / API
raw: JsonObject = {"grid_id": "demo", "columns": [{"key": "name", "label": "Name"}]}
spec = parse_grid_view_spec_json(raw)

# Typed wire literal
wire: GridViewSpecWire = {"grid_id": "demo", "columns": [{"key": "name", "label": "Name"}]}
spec = parse_grid_view_spec(wire)

artifact = GridRenderer.build(spec, rows)
```

See Python types (see section: reference/python-types.md) for all public imports.

## LLM / chat output

Allowed: `view` object matching this schema.  
Forbidden: `rows`, numeric KPI values, raw `echarts_option`, SQL text.

See Chat visualizer (see section: guides/chat-visualizer.md).


---


<!-- source: reference/python-types.md -->

# Python types (for host projects)

django-grid-view ships with **`py.typed`** (PEP 561). In a consuming Django app, enable strict pyright or mypy and **import contracts from this package** — do not copy TypedDict shapes or use `dict[str, Any]` at domain boundaries.

Full API surface: `django_grid_view.types` (see `types.__all__` in source).

## Quick reference

| Use case | Import from |
|----------|-------------|
| Table rows | `django_grid_view.types` → `RowDict` |
| Python-built grid | `GridViewSpec`, `ColumnSpec`, `KpiSpec`, `ChartSpec`, `SeriesSpec` |
| LLM / JSON spec | `GridViewSpecWire`, `ColumnSpecWire`, `JsonObject`, `ViewSpecInput` |
| Chat / frontend payload | `GridArtifactJson`, `GridLayoutDict` |
| Simple Table | `django_grid_view.tables` → `Column`, `SimpleTableConfig` |
| Render | `django_grid_view.render` → `build_artifact_from_view`, `parse_grid_view_spec` |

## Short imports (root package)

Common symbols are re-exported from `django_grid_view`:

```python
from django_grid_view import (
    GridViewSpec,
    GridViewSpecWire,
    GridArtifactJson,
    JsonObject,
    RowDict,
    ViewSpecInput,
    build_artifact_from_view,
    parse_grid_view_spec,
)
```

Prefer `django_grid_view.types` when you need enums, wire helpers, or the full list below.

## Domain models (Python builders)

```python
from django_grid_view.types import (
    BlockType,
    ChartSpec,
    ChartType,
    ColumnFormat,
    ColumnSpec,
    GridViewSpec,
    KpiAggregate,
    KpiSpec,
    KpiTone,
    RowDict,
    SeriesSpec,
    ViewLayout,
)
from django_grid_view.render import GridRenderer, build_artifact_from_view

rows: list[RowDict] = [{"name": "Ada", "amount": 10}]

spec = GridViewSpec(
    grid_id="demo",
    columns=(ColumnSpec(key="name", label="Name"), ColumnSpec(key="amount", label="Amount")),
    kpis=(KpiSpec(label="Total", column_key="amount", aggregate=KpiAggregate.SUM),),
    charts=(
        ChartSpec(
            id="main",
            chart_type=ChartType.BAR,
            x_key="name",
            series=(SeriesSpec(key="amount", label="Amount"),),
        ),
    ),
)

artifact = GridRenderer.build(spec, rows)
```

## LLM / API JSON (snake_case wire)

```python
from django_grid_view.types import (
    ChartSpecWire,
    ColumnSpecWire,
    GridViewSpecWire,
    JsonObject,
    ViewSpecInput,
)
from django_grid_view.render import (
    build_artifact_json_from_view,
    parse_grid_view_spec,
    parse_grid_view_spec_json,
)

# Untyped JSON from json.loads or Router
def handle_llm_payload(raw: JsonObject, rows: list[RowDict]) -> ...:
    return build_artifact_json_from_view(raw, rows)

# Typed wire literal (tests, planners, presenters)
wire: GridViewSpecWire = {
    "grid_id": "doctors",
    "columns": [{"key": "name", "label": "Name"}],
    "kpis": [{"label": "Count", "aggregate": "count"}],
}
spec = parse_grid_view_spec(wire)
artifact_json = build_artifact_json_from_view(spec, rows)  # GridViewSpec also works
```

`parse_grid_view_spec_json(raw)` accepts `JsonObject` only. For `GridViewSpecWire`, use `parse_grid_view_spec`.

## Outbound JSON (frontend / chat UI)

CamelCase keys match `GridView.init` and SSE payloads:

```python
from django_grid_view.types import GridArtifact, GridArtifactJson, RowDict

artifact: GridArtifact = ...
payload: GridArtifactJson = artifact.to_json()
```

## Simple Table (server-rendered)

```python
from django_grid_view.tables import Align, Column, ColumnGroup, SimpleTableConfig
from django_grid_view.types import RowDict

rows: list[RowDict] = [{"sku": "A1", "name": "Widget"}]
config = SimpleTableConfig(
    grid_id="products",
    columns=[Column(key="sku", label="SKU"), Column(key="name", label="Name")],
    data=rows,
)
```

## `ViewSpecInput`

Type alias for `build_artifact_from_view` / `build_artifact_json_from_view`:

```text
GridViewSpec | JsonObject
```

Import: `from django_grid_view.types import ViewSpecInput`

For `GridViewSpecWire`, call `parse_grid_view_spec(wire)` first, then `GridRenderer.build(spec, rows)`.

## Stability

| Module | Status |
|--------|--------|
| `django_grid_view.types` | Public — semver applies to names in `types.__all__` |
| `django_grid_view.types.spec_wire` | Public wire contract (`schema/grid-view-spec.v1.json`) |
| `django_grid_view.types.artifact_bind` | Public camelCase artifact JSON |
| `django_grid_view.tables` | Public Simple Table API |
| `django_grid_view.render` | Public render/build helpers |
| `django_grid_view` (root re-exports) | Public convenience imports |
| `django_grid_view.templatetags` | Public template tags (untyped Django surface) |
| `django_grid_view.export` | Optional extra `[static-charts]` |

Internal modules (`render.spec_parser`, `export._matplotlib_*`) may change without notice.

## Host project setup

```toml
# pyproject.toml
dependencies = ["django-grid-view>=1.0.0"]
```

```toml
# pyproject.toml — strict checking (recommended)
[tool.basedpyright]
typeCheckingMode = "strict"
```

```python
# settings.py
INSTALLED_APPS = ["django_grid_view"]
```

No separate types-stubs package is required.

## See also

- GridViewSpec JSON reference (see section: reference/grid-view-spec.md) — field tables and schema file
- Grid View artifacts (see section: grid-view-artifacts.md) — render flow
- Chat visualizer (see section: guides/chat-visualizer.md) — planner-driven presenter


---


<!-- source: guides/chat-visualizer.md -->

# Chat visualizer wire contract

How a Django chat app turns analytics SQL results into a **`grid_view`** UI component using django-grid-view.

Router prompts, graph wiring, and component extraction live in **your application**. This package provides `build_artifact_from_view` and `GridView.init`.

---

## End-to-end flow

```
User question
  → Router graph: planner → SQL tool → verifier → end
  → Django: extract_components(graph_state)
       SQL rows + planner hints → GridViewSpec (Python)
       → build_artifact_from_view(spec, rows) → GridArtifact
  → SSE/JSON: { "type": "grid_view", "artifact": …, "rows": … }
  → Frontend: renderGridView → GridView.init({ root, artifact })
```

**Invariant:** row data and KPI numbers never come from a dedicated “visualizer” LLM. SQL returns rows; your Django code builds the view spec and resolves aggregates.

---

## Recommended: planner-driven presenter (no visualizer LLM)

Use a Router graph with **planning, SQL execution, and verification only** — no extra node whose job is to emit a full grid layout.

Typical graph shape:

1. **Planner** (`llm`, JSON output) — produces the query and how to present results.
2. **SQL tool** — runs the query; state gets `columns` and `rows`.
3. **Verifier** (`llm`, optional loop) — checks the result; on success the graph ends.

### Planner output (not `GridViewSpec`)

The planner model returns JSON with SQL and presentation hints only:

```json
{
  "sql": "SELECT name AS doctor_name, COUNT(*) AS total_records FROM …",
  "purpose": "Doctors with the most rejections",
  "format": "table"
}
```

| `format` | Typical layout (built in your app) |
|----------|-------------------------------------|
| `table` | title, kpis, table |
| `report` | title, kpis, chart, table |
| `chart` | title, kpis, chart, table |
| `answer` | title, kpis |

Define a strict JSON schema (`sql`, `purpose`, `format`) in the planner prompt.  
**Forbidden in planner output:** `rows`, KPI values, a `view` object, `echarts_option`.

After the SQL step, graph state should expose result rows (e.g. under `final_output` or `intermediate_results` for the tool node) plus planner fields for `purpose` and `format`.

### Python presenter

In your app (e.g. `extract_components`):

1. Read SQL result (`columns`, `rows`) from the Router payload.
2. Read `purpose` and `format` from the planner step.
3. Build a `GridViewSpec` wire dict in Python — column labels, KPI candidates, chart choice, `layout.blocks` from `format` and column types.
4. Build a typed wire dict (`GridViewSpecWire`) or `GridViewSpec`, then `build_artifact_from_view(spec, rows)` → resolved `GridArtifact`.
5. Emit one chat component:

```python
{
    "type": "grid_view",
    "title": "…",
    "artifact": artifact.to_json(),
    "columns": [...],
    "rows": [...],
}
```

On the Router `result` event, map graph state → `components` and stream or return JSON to the browser.

### Frontend

Register a `grid_view` widget that mounts KPI/chart/table DOM from `artifact` and calls:

```javascript
GridView.init({ root: wrap, artifact: c.artifact });
```

Load `grid-view.js` and ECharts on the chat page (same bundle as dashboards).

### Package API

```python
from django_grid_view.types import GridArtifactJson, GridViewSpecWire, JsonObject, RowDict
from django_grid_view.render import GridRenderer, build_artifact_from_view, parse_grid_view_spec

sql_rows: list[RowDict] = [...]

# Presenter builds wire spec in Python
view: GridViewSpecWire = {"grid_id": "analytics", "columns": [...], "kpis": [...]}
artifact = GridRenderer.build(parse_grid_view_spec(view), sql_rows)

# Or loose JSON / visualizer LLM output
raw: JsonObject = {"grid_id": "analytics", "columns": [...]}
artifact = build_artifact_from_view(raw, sql_rows)

payload: GridArtifactJson = artifact.to_json()
```

Imports: Python types (see section: reference/python-types.md).

---

## Alternative: dedicated visualizer LLM node

Some integrations add a **second LLM node** after SQL that emits layout JSON. The model returns structure only:

```json
{
  "view": {
    "grid_id": "analytics-result",
    "columns": [{ "key": "doctor_name", "label": "Doctor" }],
    "kpis": [{ "label": "Records", "aggregate": "count" }],
    "charts": [],
    "layout": { "blocks": ["title", "kpis", "table"] }
  }
}
```

Use a generic LLM node with `output_format: json`. Avoid legacy platform-specific `sql_visualizer` executors unless you still depend on them.

**Forbidden** in visualizer output: `rows`, row data, numeric KPI values, `echarts_option`, SQL text.

You must still call `build_artifact_from_view(view, sql_rows)` in Django so numbers come from SQL rows, not the model.

---

## Choosing an approach

| Approach | Graph | LLM emits | Who builds `GridViewSpec` |
|----------|-------|-----------|---------------------------|
| **Planner-driven presenter** | planner → SQL tool → verifier | `sql`, `purpose`, `format` | Your Python presenter |
| **Visualizer LLM** | planner → SQL tool → visualizer → … | `{ "view": … }` | Visualizer LLM, then `build_artifact_from_view` |

Checklist:

1. **Graph** — no visualizer node unless you need model-chosen layouts.
2. **Planner prompt** — SQL + `format`, not a full grid spec (GridViewSpec reference (see section: reference/grid-view-spec.md) documents the presenter output).
3. **Adapter** — graph state → `grid_view` component; see Grid View artifacts (see section: grid-view-artifacts.md).
4. **Frontend** — `GridView.init({ artifact })` in the chat panel.


---


<!-- source: guides/dashboard-builders.md -->

# Dashboard builders

Replace ad hoc `ChartSpec + rows + KpiSpec` tuples with one `GridViewSpec` and `build_artifact_from_view`.

## Before

```python
chart_spec, chart_rows = build_finance_chart(tab_id, departments)
kpi_specs, kpi_rows = build_finance_kpis(departments)
# template: render_kpi_strip + render_chart
```

## After

```python
from django_grid_view.types import GridViewSpecWire, RowDict
from django_grid_view.render import GridRenderer, parse_grid_view_spec

def build_finance_artifact(tab_id: str, departments: list[DepartmentStats]):
    rows: list[RowDict] = finance_rows(departments)
    view: GridViewSpecWire = {
        "grid_id": f"finance-{tab_id}",
        "title": "Finance by department",
        "columns": [...],
        "kpis": [...],
        "charts": [...],
        "layout": {"blocks": ["title", "kpis", "chart"]},
    }
    return GridRenderer.build(parse_grid_view_spec(view), rows)
```

Template:

```django
{% render_grid_view artifact %}
```

## When to migrate

| Pattern | Migrate? |
|---------|----------|
| KPI + chart + table on one page | Yes |
| Single chart, no KPI | Optional |
| Custom `Column.render()` HTML | Keep `SimpleTableConfig` |

Use `GridViewSpecWire`, `RowDict`, and `parse_grid_view_spec` / `GridRenderer.build` — see Python types (see section: reference/python-types.md). Avoid `dict[str, Any]` at the domain boundary.


---


<!-- source: changelog.md -->

# Changelog

Release history for **django-grid-view**. Keep this file in sync with root [`CHANGELOG.md`](https://github.com/alpiua/django-grid-view/blob/main/CHANGELOG.md) and [GitHub Releases](https://github.com/alpiua/django-grid-view/releases).

## How to document a new release

Use **one page** — this `changelog.md` — unless a release needs a long upgrade guide.

| What | Convention | Example |
|------|------------|---------|
| Nav label | Always **Changelog** | `changelog.md` in `mkdocs.yml` |
| Section heading | `## X.Y.Z` (semver, no `v` prefix in heading) | `## 1.1.0` |
| Git tag | `v` + same version | `v1.1.0` |
| PyPI version | Matches tag without `v` | `1.1.0` in `pyproject.toml` |
| Date (optional) | Under the heading | `**2026-05-31** — short title` |
| Breaking changes | Subsection `### Changed` or `### Removed` | Call out template/API breaks |

Add the **newest version at the top** (below this “How to document” block, or move these maintainer notes to `dev/` if the section grows).

**When to add a separate page** (not required for most releases):

| Situation | Suggested filename | Nav entry |
|-----------|-------------------|-----------|
| Major upgrade with many steps | `docs/upgrading-to-1-2.md` | “Upgrading to 1.2” under User guide |
| One-off migration from old package name | `docs/upgrading-from-grid-table.md` | Only while that audience exists |

Prefer a subsection here (`### Migration`) for small notes; a dedicated page only if it would exceed ~100 lines.

---

## 1.0.1

**2026-05-31** — Docs, agent skill, and repository hygiene.

### Added

- `.githooks/commit-msg` removes Cursor `Co-authored-by` trailers on commit
- Richer docs CSS (section headings, nav labels)

### Changed

- README and Agent skill (see section: llm/skill.md) aimed at production use
- LLM bundle (see section: llm/django-grid-view-llm-context.md) linked directly from context page (see section: llm/context.md)
- MkDocs navigation and TOC improvements

## 1.0.0

**2025** — Grid View 1.0 release.

### Added

- Unified **Grid View 1.0**: `GridViewSpec`, `GridRenderer`, `GridArtifact`, `{% render_grid_view %}`
- **Simple Table** in `django_grid_view.tables` with sort, search, export
- **KPI** and **ECharts** chart rendering (`render_kpi_strip`, `render_chart`)
- **AG-Grid KPI** client aggregates via `render_grid_kpi_strip` + `GridView.createAgGridAdapter`
- Single browser bundle `grid-view.js` (replaces `simple-table.js`)
- JSON Schema `schema/grid-view-spec.v1.json` for LLM-friendly specs
- i18n catalog (en + uk) via Django locale

### Changed

- PyPI package renamed from `django-grid-table` to **`django-grid-view`**
- Python module renamed from `django_grid_table` to **`django_grid_view`**
- Template tags moved to `django_grid_view` namespace


---
