Defining and Seeding Dashboards

Developer guide for the Enezaframework dashboard system. Part of the Dashboards series.

Table of Contents

  1. Overview
  2. Where Definitions Live
  3. Definition Skeleton
  4. Dashboard Keys
  5. Tab Keys
  6. Widget Keys and Defaults
  7. Widget Types at a Glance
  8. Layout Rules
  9. Parameters, Filters, and Data Settings
  10. Seeding
  11. Publication Behavior
  12. Ownership and Versioning
  13. Repository Workflow
  14. Related Guides

Overview

A declarative definition is a Python dictionary that describes a dashboard, its tabs, and its widgets. Seeding turns that dictionary into rows:

flowchart LR
    DEF[AppConfig.dashboards<br/>or settings or registry] --> LOAD[load_definitions]
    LOAD --> VALIDATE[validate_definition]
    VALIDATE --> SEED[seed_definitions]
    SEED --> DASH[Dashboard]
    SEED --> VER[Draft DashboardVersion]
    SEED --> TAB[DashboardTab]
    SEED --> WIDGET[DashboardWidget]
    SEED --> PUB[Publish when requested]

Definitions are declarative, not imperative: you describe the desired end state, and re-running the seed is safe. Identity is stable, defaults are filled in, and omitted tabs/widgets are pruned unless you opt out (enezadashboard/services/seeding.py:94-118, enezadashboard/services/seeding.py:210-325).

You will build: a DASHBOARDS list for your app, seed it, and publish a dashboard.


Where Definitions Live

There are three supported sources, all loaded by enezadashboard/definitions.py:22-55:

Source Best for How
AppConfig.dashboards Normal app-owned dashboards. Set dashboards = DASHBOARDS on the app config.
settings.ENEZA_DASHBOARD_DEFINITIONS Project-wide or deployment-specific dashboards. A list of dicts, import paths, or callables.
register_dashboard_definition() Dynamic registration from code. from enezadashboard.registry import register_dashboard_definition.

frameworkapp uses the first form:

python
# frameworkapp/apps.py
from django.apps import AppConfig
from .dashboards import DASHBOARDS


class FrameworkappConfig(AppConfig):
    name = "frameworkapp"
    dashboards = DASHBOARDS

When a definition comes from an AppConfig, the loader fills in module from the app label if it is missing (enezadashboard/definitions.py:31-37).

Precedence and conflicts:

  • All sources are loaded.
  • Duplicate (module, code) identities raise ValueError during loading (enezadashboard/definitions.py:43-54).
  • A definition is not merged with another definition; one identity must come from one source.

Definition Skeleton

python
DASHBOARDS = [
    {
        "module": "myapp",
        "code": "sales_overview",
        "name": "Sales overview",
        "description": "Sales volume, mix, and item detail.",
        "is_active": True,
        "is_default": True,
        "sort_order": 0,
        "managed_by": "seed",
        "layout_template": "",
        "default_report_ref": "myapp.salesbyitem",
        "access_code": "",
        "seed_revision": "2026-10-24",
        "publish": True,
        "tabs": [
            {
                "code": "overview",
                "name": "Overview",
                "description": "",
                "icon": "squares-2x2",
                "sort_order": 0,
                "layout": {"columns": 12},
                "filter_config": {
                    "date_range": {"enabled": True}
                },
                "widgets": [
                    {
                        "code": "total_value",
                        "title": "Total value",
                        "type": "kpi",
                        "report_ref": "myapp.salesbyitem",
                        "display_config": {
                            "measure": "sum__value",
                            "aggregation": "sum",
                            "format": "currency",
                            "decimals": 2,
                        },
                        "layout": {"x": 0, "y": 0, "w": 3, "h": 2},
                        "sort_order": 0,
                        "is_active": True,
                    },
                ],
            },
        ],
    },
]

The live reference is frameworkapp/dashboards.py:1-117, walked through in Worked Example: frameworkapp.


Dashboard Keys

Key Required Default Meaning
module Yes App label when loaded from an AppConfig Installed Django app that owns the dashboard.
code Yes — Slug; unique with module.
name Yes — Display name.
description No "" Long description.
is_active No True Inactive dashboards are not visible.
is_default No False The module default for users without a preference. Only one active default per module.
sort_order No 0 Ordering in lists.
managed_by No seed seed or admin. Controls whether future seeds may update it.
layout_template No "" Wireframe code. Must exist when set. Recorded for provenance only; seeded tabs/widgets are authoritative.
default_report_ref No "" Stable report code. Must resolve.
access_code No "" Optional RBAC permission required to view the dashboard.
seed_revision No "" Free-form revision marker stored on the dashboard.
publish No True for new dashboards Publish after seeding. See Publication Behavior.
tabs No [] Tab definitions.

Validation rules (enezadashboard/services/seeding.py:121-207):

  • module must be an installed app.
  • code must be a valid slug.
  • name is required.
  • managed_by must be seed or admin.
  • layout_template, when set, must be a known wireframe.
  • default_report_ref, when set, must resolve to an eligible data source.

Data settings are not part of the seed schema. Dashboard.data_settings holds reference_date_field, limit, and default_period. The seeding code does not map them from a definition (enezadashboard/services/seeding.py:230-241). Set them in the designer's dashboard form, or extend the seeding service if they must be code-controlled. Do not assume a data_settings key in a definition will take effect.


Tab Keys

Key Required Default Meaning
code Yes — Stable tab code; unique within the version.
name Yes — Display name.
description No "" Tooltip/description.
icon No "" One of the tab icon keys. Seed validation rejects unknown values.
sort_order No 0 Tab order.
layout No {} Tab layout metadata; the designer stores {"columns": 12} for wireframes.
filter_config No {} Date range defaults and shared parameter/data settings.
widgets No [] Widget definitions.

Tab icons must come from the fixed list in enezadashboard/icons.py:1-18:

"", squares-2x2, chart-bar, chart-pie, arrow-trending-up, arrow-trending-down, cube, truck, banknotes, Users, building-office, shopping-cart, document-text, clipboard-document-list, cog-6-tooth, shield-check.

A tab with no active widget cannot be published; publication validates at least one active widget per tab (enezadashboard/services/validation.py:109-112).


Widget Keys and Defaults

Key Required Default Meaning
code Yes — Stable widget code; unique within the tab.
title Yes — Display title.
type or widget_type Yes — kpi, chart, table, or report_preview.
report_ref Yes — Stable data-source code.
description No "" Subtitle/description.
display_config No {} Type-specific settings. See the reference.
layout No By type: KPI 3×2, chart 6×4, preview 6×4, table 12×4 Grid position.
parameter_config No {} Report parameter values. Keys must be allowed parameter names.
filter_config No {} Query-builder filter rules for this widget.
sort_order No 0 Ordering inside the tab.
is_active No True Inactive widgets are skipped and not published.

type and widget_type are aliases; _widget_type() accepts either (enezadashboard/services/seeding.py:44-45).

Every active widget must reference a report and pass that widget type's validation. Unknown widget types fail validation (enezadashboard/services/validation.py:114-138).


Widget Types at a Glance

Type Renders Required display_config Full reference
kpi A single aggregate with optional sparkline and comparison. measure (unless aggregation is count). KPI
chart Server-rendered SVG chart. category_field and value_field; gauge and other types add fields. Chart
table Paginated, sortable data grid. None; columns default to the first six report columns. Table
report_preview A short, non-paginated table. None; columns default to the first six. Report preview

A minimal set:

python
"widgets": [
    {
        "code": "total_value",
        "title": "Total value",
        "type": "kpi",
        "report_ref": "myapp.salesbyitem",
        "display_config": {
            "measure": "sum__value",
            "aggregation": "sum",
        },
        "layout": {"x": 0, "y": 0, "w": 3, "h": 2},
    },
    {
        "code": "value_by_item",
        "title": "Value by item",
        "type": "chart",
        "report_ref": "myapp.salesbyitem",
        "display_config": {
            "chart_type": "bar",
            "category_field": "item_code",
            "value_field": "sum__value",
            "limit": 10,
            "sort": "desc",
        },
        "layout": {"x": 3, "y": 0, "w": 9, "h": 4},
    },
    {
        "code": "detail",
        "title": "Item detail",
        "type": "table",
        "report_ref": "myapp.salesbyitem",
        "display_config": {
            "columns": ["item_code", "item_description", "sum__quantity", "sum__value"],
            "page_size": 10,
        },
        "layout": {"x": 0, "y": 4, "w": 12, "h": 5},
    },
]

Layout Rules

The layout is a 12-column grid:

Property Rule Source
x Non-negative; must satisfy x + w <= 12. enezadashboard/services/validation.py:28-49
y Non-negative. The canvas endpoint accepts 0–200. enezadashboard/views/designer.py:645-654
w 2–12. enezadashboard/services/validation.py:44-45
h 2–8. enezadashboard/services/validation.py:46-47

Example positions:

python
{"x": 0, "y": 0, "w": 3, "h": 2}   # KPI
{"x": 3, "y": 0, "w": 9, "h": 4}   # chart
{"x": 0, "y": 4, "w": 12, "h": 5}  # full-width table

Publication rejects invalid layouts with a path such as tabs[0].widgets[2].


Parameters, Filters, and Data Settings

Widget parameters

parameter_config supplies report form values:

python
"parameter_config": {
    "start_date": "2026-01-01",
    "end_date": "2026-03-31",
}

Allowed keys come from the report's serialized parameters:

  • DateRangeParam → start_date, end_date;
  • other parameters → their name and form_fields.

Unknown keys fail validation (enezadashboard/services/validation.py:52-62, enezadashboard/services/seeding.py:202-206).

Widget filters

filter_config holds query-builder rules:

python
"filter_config": {
    "condition": "AND",
    "groups": [
        {
            "condition": "AND",
            "rules": [
                {"id": "item_code", "operator": "equal", "value": "A-100"}
            ],
        }
    ],
}

Every rule id must appear in the report's metadata filter_fields, and the operator must be supported by the workflow engine (enezareporting/dashboard.py:433-466).

Tab date range

python
"filter_config": {
    "date_range": {
        "enabled": True,
        "default_period": "last_30_days",
    }
}

For a custom range:

python
"date_range": {
    "enabled": True,
    "default_period": "custom",
    "default_start_date": "2026-01-01",
    "default_end_date": "2026-03-31",
}

Tab defaults override the dashboard default; the renderer falls back to the dashboard's data_settings.default_period and then the site default (enezadashboard/services/renderer.py:218-254).

Tab shared parameter and data settings

The tab filter_config may also carry:

  • parameter_defaults: values applied to every widget before widget-level parameter_config;
  • data: reference_date_field and limit, which override the dashboard-level data_settings for this tab.

Data settings merge in this order: dashboard data_settings, then tab filter_config.data. Parameters merge in this order: tab filter_config.parameter_defaults, then widget parameter_config, then non-date request query values, then the resolved period dates (enezadashboard/services/renderer.py:222-231, enezadashboard/services/renderer.py:688-731).

Dashboard data settings (designer only)

Setting Stored key Effect
Reference date field data_settings.reference_date_field Overrides the report's date field when applying periods.
Data load limit data_settings.limit Caps rows loaded by table, chart, and preview widgets. KPI/gauge aggregates are not capped.
Default period data_settings.default_period Default date range when a tab does not define one.

These are edited in the designer dashboard form and are not part of the seed schema (enezadashboard/forms.py:274-292).


Seeding

Run the management command:

bash
python manage.py seed_dashboards --dry-run --json
python manage.py seed_dashboards
python manage.py seed_dashboards --publish

Options (enezadashboard/management/commands/seed_dashboards.py:15-24):

Flag Effect
--module <label> Restrict to one module.
--dashboard <code> Restrict to one dashboard code.
--dry-run Validate and report; write nothing.
--json Machine-readable result.
--force Update admin-owned dashboards.
--publish Publish updated definitions whose draft changed.
--no-prune Keep tabs/widgets omitted from the definition.
--deactivate-missing Deactivate seed-owned dashboards not present in the definitions. Ignored when --dashboard is set.
--continue-on-error Keep processing after an invalid definition.

Dry-run output reports either Would seed ... or Would skip ... (administrator-owned) and exits with an error if validation fails (enezadashboard/management/commands/seed_dashboards.py:42-70).

A successful run reports counts:

text
Dashboards: 1 created, 0 updated, 0 skipped, 1 published, 0 pruned.

Publication Behavior

  • A new seeded dashboard is published by default unless the definition sets "publish": false (enezadashboard/services/seeding.py:312-324).
  • An existing dashboard is published only when publish is truthy in the definition or --publish is passed.
  • Publishing is skipped when the published and draft signatures are identical, so re-seeding an unchanged definition does not create versions.
  • Publication validates the draft first. Invalid definitions fail with invalid_definition from the seed command, and invalid drafts fail with publish_invalid from publication (enezadashboard/services/publication.py:68-101).

Ownership and Versioning

managed_by Who owns it Seed behavior
seed The definition. Updated on every run.
admin An administrator. Skipped unless --force.

Designer-created dashboards default to admin (Dashboard.managed_by default is admin in enezadashboard/models/dashboard.py:55-60). The designer's Take ownership action switches a seeded dashboard to admin, protecting it from future seed updates (enezadashboard/services/publication.py:154-157).

Stable identity:

  • dashboard: (module, code);
  • tab: (version, code);
  • widget: (tab, code).

Database primary keys are never external identity. Re-running the same definition creates no duplicates.

Pruning is on by default: tabs and widgets omitted from the definition are deleted from the draft. Use --no-prune when a definition is intentionally partial.


Repository Workflow

  1. Add or edit the report in myapp/views/reports.py.
  2. Add or edit myapp/dashboards.py.
  3. Wire dashboards = DASHBOARDS into myapp/apps.py.
  4. Run python manage.py seed_dashboards --dry-run --json.
  5. Fix every validation error.
  6. Run python manage.py seed_dashboards.
  7. Run python manage.py rbac_sync when report permissions changed.
  8. Open /dashboards/<module>/ and verify the published dashboard.
  9. Commit the report, definitions, and AppConfig change together.

Do not edit a seed-owned dashboard in the designer and then re-seed without taking ownership; the next seed overwrites the edits.