AG-Grid integration¶
Extension layer for AG Grid Community 31.x in Django: toolbar,
AgGridHost, session persistence, infinite-model HTTP contract, server XLSX sync.
| Component | Owner |
|---|---|
gridApi, columnDefs, cell renderers, filter components |
Host |
| JSON data API, ORM field maps, row serialization | Host |
AgGridPageSpec, XLSX column resolution |
Host (spec) + package (helpers) |
AgGridHost, toolbar, modal, search bar |
Package |
GridView.AgGrid, grid_view_spec.backends.django.ag_grid |
Package |
GridPreference (named presets, saved searches) |
Package model; host mounts save URL |
CDN pins (conf.py)¶
AG-Grid and SortableJS load from CDN on AG-Grid pages. Versions are pinned in Django settings, not hardcoded in templates — host apps can override for self-hosting or semver bumps without forking JS.
| Setting | Default | Purpose |
|---|---|---|
GRID_VIEW_SPEC_AG_GRID_VERSION |
31.3.4 |
AG-Grid Community semver (jsDelivr URL built automatically) |
GRID_VIEW_SPEC_AG_GRID_CDN_URL |
(built from version) | Full script URL override (self-hosted mirror) |
GRID_VIEW_SPEC_SORTABLE_VERSION |
1.15.2 |
SortableJS for column-settings drag-reorder |
GRID_VIEW_SPEC_SORTABLE_CDN_URL |
(built from version) | Full Sortable script URL override |
GRID_VIEW_SPEC_ECHARTS_VERSION |
5.5.1 |
ECharts semver (host base template) |
GRID_VIEW_SPEC_ECHARTS_CDN_URL |
(built from version) | Full ECharts script URL override |
# settings.py — optional overrides
GRID_VIEW_SPEC_AG_GRID_VERSION = "31.3.4"
# GRID_VIEW_SPEC_AG_GRID_CDN_URL = "https://static.myapp.example/vendor/ag-grid-community.min.js"
GRID_VIEW_SPEC_SORTABLE_VERSION = "1.15.2"
GRID_VIEW_SPEC_ECHARTS_VERSION = "5.5.1"
Host base template (charts):
<script src="{{ echarts_cdn_url }}"></script>
{# or in view context: from grid_view_spec.backends.django.conf import echarts_cdn_url #}
Python helpers: grid_view_spec.backends.django.conf.ag_grid_cdn_url(), sortable_cdn_url(), echarts_cdn_url().
Architecture¶
flowchart TB
subgraph browser
AG[gridApi infinite model]
MGR[AgGridHost]
AJS[GridView.AgGrid]
LS[(localStorage session)]
end
subgraph host_django
COL[columnDefs TS/JS]
VIEW[page view]
API[GET data API]
XLSX[GET export/xlsx builder]
SPEC[AgGridPageSpec]
end
subgraph package
PY[ag_grid.server]
PREF[GridPreference]
end
VIEW --> MGR
COL --> AG
MGR --> AG
MGR --> LS
MGR --> PREF
AJS --> AG
AJS -->|startRow endRow filters sort q| API
API --> PY
AJS -->|export_cols q filters| XLSX
SPEC --> XLSX
Infinite data API contract¶
Response¶
| Field | Type | Rule |
|---|---|---|
data |
object[] |
One object per row; keys = columnDefs[].field / colId |
lastRow |
int |
Total row count after server filters (not page size) |
Empty keys may be omitted in sparse rows; export uses AgGridPageSpec column order, not Object.keys(row).
Request query parameters¶
| Param | Source | Type | Purpose |
|---|---|---|---|
startRow |
createInfiniteDatasource |
int | Block start (0-based) |
endRow |
createInfiniteDatasource |
int | Block end (exclusive) |
filters |
AG Grid filterModel |
JSON object | Server-side column filters |
sort |
AG Grid sort model | JSON array | Server-side sort |
q |
Search bar (manager._searchText) |
string | Quick search |
cols |
Visible columns | comma-separated | Multi-column search scope |
export_cols |
syncExportHref only |
comma-separated | XLSX column ids (not sent on infinite fetch) |
action |
Host | string | e.g. dictionary for set-filter values |
field |
Host | string | Column id when action=dictionary |
Host-specific params (period, tab, …) pass via getExtraParams in JS and replay in export builders.
Filter JSON (filters param)¶
Set filter (custom or AG set):
Text filter:
Supported text type values in apply_grid_filters: contains, notContains, equals, notEqual, startsWith, endsWith.
Pass format_filter_value(col_id, display_value) when DB values differ from filter labels.
Sort JSON (sort param)¶
Only the first entry is applied; host supplies field_map: colId → ORM path.
Dictionary action¶
Wire dictionaryUrl in gridOptions.context.
Faceted set filter (package dictionary endpoint)¶
For counts that stay in sync with every other filter and search (exclude-own), use the
package-provided api_column_filter_dictionary route plus a registered FacetSource:
GET /grid/filter-dictionary/?grid=<grid_id>&field=<col_id>&<current params>
→ { "values": [{"value": "Adult", "count": 42}, …] }
Register a FacetSource per grid at startup so the endpoint can resolve the filtered
source and count distinct values — see Faceted filtering for the
full callback contract and an NSZU example. Mount the route via
include("grid_view_spec.backends.django.urls") — Django integration.
Python: InfiniteGridParams¶
from grid_view_spec.backends.django.ag_grid import (
apply_grid_filters,
apply_grid_sort,
parse_infinite_params,
)
params = parse_infinite_params(request) # default_page_size=100
# params.start_row, params.end_row, params.search_query
# params.visible_cols, params.filters, params.sort_model
# params.action, params.action_field
AgGridPageSpec contract¶
from grid_view_spec.types.ag_grid import AgGridColumnSpec, AgGridPageSpec
PRODUCTS_PAGE_SPEC = AgGridPageSpec(
grid_id="products",
columns=(
AgGridColumnSpec("sku", "SKU"),
AgGridColumnSpec("name", "Name"),
AgGridColumnSpec("cost", "Cost", hide=True),
),
)
AgGridColumnSpec field |
Default | Meaning |
|---|---|---|
col_id |
— | AG Grid colId / row key |
label |
— | XLSX header, UI labels |
hide |
False |
Default visibility when no live grid snapshot |
exportable |
True |
Included in export resolution |
resolve_export_columns(spec, request) input |
Result |
|---|---|
export_cols=a,b,c (subset of exportable ids) |
Those ids in request order |
export_cols absent or empty |
All columns with exportable=True and hide=False |
Constant: EXPORT_COLS_PARAM = "export_cols".
JavaScript contract¶
Load assets once per page (base template, outside HTMX swaps):
{% load grid_view_spec %}
{% grid_view_spec_assets part='css' %}
{% grid_view_spec_assets part='js' force_core=True %}
AG Grid Community + SortableJS from CDN in the same base template.
Boot sequence¶
sequenceDiagram
participant T as scripts.html
participant M as AgGridHost
participant G as gridApi
participant A as GridView.AgGrid
participant S as data API
T->>M: initGrid(container, gridOptions)
M->>G: createGrid
Note over G: columnDefs may be late-bound
G->>G: setGridOption columnDefs
M->>M: reapplyPersistedState()
G->>A: setGridOption datasource
A->>S: GET startRow endRow filters sort q
S-->>A: data lastRow
A->>G: successCallback
manager.gridApi.setGridOption("columnDefs", ProductColumns);
manager.reapplyPersistedState();
manager.gridApi.setGridOption(
"datasource",
GridView.AgGrid.createInfiniteDatasource({
url: "/api/products-data/",
manager: manager,
getExtraParams: () => ({ period: "2025-01" }),
onLastRow: (count) => { /* toolbar counter */ },
})
);
gridOptions.context¶
| Key | Default | Contract |
|---|---|---|
gridId |
— | Equals grid_id in scripts.html and GridPreference.grid_id |
storageScope |
— | Suffix: agGridState_{gridId}__{storageScope} |
syncUrlState |
true |
history.replaceState for q, filters, urlPageStateKeys |
urlPageStateKeys |
keys from getPageState() |
Domain params mirrored to URL |
getPageState() |
— | Returns { period: ["2024-01"], … } for filter bar |
applyPageState(state, opts) |
— | Restores domain filter bar |
onFilterChanged(manager) |
— | Hook after AG filter change (export sync) |
onDataReload(manager) |
— | Hook after manual search reload; otherwise export links are synced automatically |
restoreQuickFilter |
true |
Restore search from localStorage when URL has no q |
dictionaryUrl |
— | Base URL for set-filter dictionary fetch |
Export href sync¶
GridView.AgGrid.syncExportHref(linkEl, manager, {
getExtraParams: () => ({ period: "2025-01" }),
exportColumns: true, // writes export_cols (default true)
});
syncExportHref accepts either a runtime handle or a grid id string. syncExportLinks
updates every [data-cm-export-sync][data-cm-grid-id="…"] link for the grid.
GridView.AgGrid.syncExportHref(linkEl, "products", {
getExtraParams: () => ({ period: "2025-01" }),
});
GridView.AgGrid.syncExportLinks("products");
Call on first load and on filter/column/search changes; invoke again in link onclick before navigation.
syncExportHref option |
Default | Effect |
|---|---|---|
exportColumns |
true |
Visible column ids → export_cols |
includeVisibleCols |
false |
Also set cols on export URL |
Page wiring¶
Template (GridViewSpec v2)¶
{% load grid_view_spec %}
{% render_grid_view_spec page.spec page.rows %}
{% grid_view_spec_assets part='ag_grid' %}
For hand-wired AG-Grid pages (without full spec render), load assets in base layout:
{% grid_view_spec_assets part='css' %}
{# … page body with #products-grid … #}
{% grid_view_spec_assets part='js' force_core=True %}
{% grid_view_spec_assets part='ag_grid' %}
Column settings and export hrefs use {% grid_view_spec_assets %} with toolbar blocks.
Host view¶
import json
from grid_view_spec.backends.django.models import GridPreference
def products_view(request):
presets, searches = {}, []
if request.user.is_authenticated:
pref = GridPreference.objects.filter(user=request.user, grid_id="products").first()
if pref:
presets, searches = pref.col_presets, pref.searches
return render(request, "products.html", {
"ag_grid_presets": json.dumps(presets),
"ag_grid_searches": json.dumps(searches),
})
Save preferences URL (host-mounted)¶
Package ships save_grid_settings view; host registers it with name api_grid_preferences:
from grid_view_spec.backends.django.views import save_grid_settings
urlpatterns = [
path("api/grid/preferences/", save_grid_settings, name="api_grid_preferences"),
]
XLSX link¶
<a href="{% export_xlsx_href 'products' period=period_filter %}"
id="products-xlsx"
onclick="syncProductsExport();">XLSX</a>
Persistence¶
Column settings UI lives in grid-view.min.js (GridView.createColumnSettings). AgGridHost delegates after gridApi init; Simple Table uses the DOM adapter. See Column settings.
Three storage layers:
flowchart LR
subgraph session [Session — localStorage]
CS[colState]
FS[filterState]
QF[quickFilter]
PS[pageState]
end
subgraph named [Named — localStorage + GridPreference]
CP[col_presets]
SR[searches]
end
subgraph url [Shareable URL]
UQ[q]
UF[filters]
UP[period etc]
end
session --> url
MGR[AgGridHost] --> session
MGR --> named
| Layer | Key / model | Contents |
|---|---|---|
| Session | agGridState_{grid_id} or …__{storageScope} |
colState, filterState, quickFilter, pageState |
| Named presets | GridPreference.col_presets |
User-named getColumnState() snapshots |
| Saved searches | GridPreference.searches |
Quick-search bookmark strings |
Session layout auto-saves on column/filter/search/domain-filter changes. Named presets and searches save via POST api_grid_preferences. Last column layout is not auto-persisted to Django.
Session JSON¶
{
"colState": [{ "colId": "name", "width": 220, "hide": false }],
"filterState": { "customer": { "values": ["Ada"] } },
"quickFilter": "uuid-fragment",
"pageState": { "period": ["2024-01", "2024-02"] }
}
Restore priority¶
| Field | Source order |
|---|---|
| Quick search | URL ?q= → localStorage |
| AG filters | URL ?filters= (JSON) → localStorage |
| Domain filters | URL keys in urlPageStateKeys → pageState in localStorage |
Cross-tab: storage event on session key reapplies state and purges infinite cache.
See Saved preferences for POST body schema.
Integration checklist¶
| # | Requirement |
|---|---|
| 1 | AG Grid + Sortable CDN in base template |
| 2 | {% grid_view_spec_assets part='js' force_core=True %} once per page |
| 3 | grid_id = context.gridId = GridPreference.grid_id |
| 4 | ag_grid_presets, ag_grid_searches in view context |
| 5 | api_grid_preferences → save_grid_settings; migrate grid_view_spec_django |
| 6 | Data API: { data, lastRow } + parse_infinite_params |
| 7 | XLSX: AgGridPageSpec + resolve_export_columns + syncExportHref |
| 8 | Late columnDefs: reapplyPersistedState() before datasource |
Runtime constraints¶
| Rule | Detail |
|---|---|
| HTMX | AG Grid CDN and bundle load outside swapped fragments |
| Search | Infinite model uses server q; not quickFilterText |
| Export | Server XLSX via registered builder — XLSX export |
| License | AG Grid Community 31.x APIs only |
Filtered KPI strip¶
See KPI and charts.
Reference: host integration¶
| Layer | Module |
|---|---|
| Spec | dashboard/items/ag_grid/spec.py |
| API | ag_grid/api.py, queryset.py, fields.py, serializers.py |
| XLSX | export.py — builder key items_grid |
| Columns | your frontend columnDefs |
| Templates | _grid_options.html, _grid_scripts.html, _toolbar.html |
Typical config: grid_id="items", storageScope="items-grid", data endpoint GET /api/items/data/.