Skip to main content

Django AskLens

PyPI Python

Django AskLens is the Django implementation of the AskLens specification: safe natural-language querying over explicitly registered Django models, with optional Django REST Framework and MCP adapters.

AskLens does not let an LLM write SQL. It asks a provider for structured JSON, validates the plan against your registered catalog and permissions, compiles a read-only Django ORM query, executes with limits, and returns table/chart-ready JSON.

Status: alpha. APIs may change before a stable release.

Django AskLens was created by Revaz Zakalashvili (revaz.zakalashvili@gmail.com).

Package provenance

What it provides

  • The AskLens specification: untrusted plans over an explicit catalog, with current-request authorization and row scope.
  • Explicit semantic resource registration.
  • Permission-scoped catalog metadata plus separate machine capabilities.
  • Optional DRF catalog, capabilities, query, and run-detail endpoints.
  • Strict Pydantic QueryPlan validation.
  • Five packaged unversioned JSON Schemas for the current spec documents.
  • A language-neutral synthetic conformance corpus replayed on SQLite and required PostgreSQL 15/18 CI jobs.
  • A source-checkout PostgreSQL 18 Compose/Playwright synthetic reference smoke.
  • ORM-only list and aggregate query execution.
  • Dummy provider for deterministic tests and demos.
  • OpenAI-compatible live provider adapter.
  • Query-run audit records.
  • Frontend-agnostic columns + data JSON output.
  • Optional packaged browser UI for demos/reference use.

0.2.0 quickstart

The example below describes the 0.2.0 release. Use it with the exact PyPI package, the immutable v0.2.0 source tag, or a locally built wheel whose source commit and SHA-256 digest you verified.

For a linear path that installs core only, wires one project-owned startup import, and executes current-request list plans, use the Core-only executable quickstart.

For the optional DRF adapter, follow the Authenticated normal-user API quickstart. It installs an exact current [api] artifact, uses a host-created authenticated user and server-assigned permission, checks that user's permission-scoped catalog first, then demonstrates the current deterministic query, opaque denial, and metadata-only audit outcomes. If the resource is absent from the catalog, diagnose host permission assignment and the single startup registration import; do not weaken the opaque query error. AskLens uses the host's existing authentication and does not provide a login or token endpoint.

Add DRF and the AskLens app:

INSTALLED_APPS = [
    # ...
    "rest_framework",
    "django_asklens",
]

Mount the API:

from django.urls import include, path

urlpatterns = [
    path("", include("django_asklens.api.urls")),
]

Run migrations for AskLens audit records:

python -m django migrate asklens

Register a resource during app startup. This example inherits the safe DEFAULT_SCOPE_MODE="context_scoped" setting shown below:

from django_asklens import Metric, register
from shop.models import Order


def visible_orders(request):
    if not getattr(request.user, "is_authenticated", False):
        return Order.objects.none()
    return Order.objects.filter(account__memberships__user=request.user)


register(
    timezone="UTC",
    model=Order,
    name="orders",
    label="Orders",
    description="Orders visible to the current user.",
    default_date_field="created_at",
    fields={
        "order_id": {
            "binding": "id",
            "type": "integer",
            "nullable": False,
            "label": "Order ID",
        },
        "status": {
            "binding": "status",
            "type": "string",
            "nullable": False,
            "label": "Status",
        },
        "created_at": {
            "binding": "created_at",
            "type": "datetime",
            "nullable": False,
            "label": "Created date",
        },
        "customer.email": {
            "binding": "customer__email",
            "type": "string",
            "nullable": False,
            "label": "Customer email",
            "sensitive": True,
            "requires_permission": "customers.view_pii",
        },
        "total_cents": {
            "binding": "total_cents",
            "type": "integer",
            "nullable": False,
            "label": "Total in cents",
        },
    },
    metrics=[
        Metric(
            "order_count",
            op="count",
            binding="id",
            result_type="integer",
            label="Orders",
        ),
        Metric(
            "revenue",
            op="sum",
            binding="total_cents",
            result_type="integer",
            label="Revenue",
        ),
    ],
    default_order=(("created_at", "desc"),),
    requires_permission="orders.view_reports",
    scope_provider=visible_orders,
)

Field mapping keys are stable public semantic names. Each field explicitly declares its private Django binding, canonical type, and nullable contract; bindings use Django __ traversal syntax and are never serialized to catalogs or providers. Every resource also declares a server-owned IANA timezone; there is no client or implicit Django TIME_ZONE fallback. Metrics own their private binding, operation, result type, permission, and relationship-cardinality policy; plans reference only the registered metric name. requires_permission on the resource gates catalog visibility and query validation for the whole resource. Field- and metric-level requires_permission gate individual members without exposing permission tokens in public metadata.

Start with the deterministic dummy provider:

DJANGO_ASKLENS = {
    "DEFAULT_SCOPE_MODE": "context_scoped",
    "LLM_BACKEND": "dummy",
    "DUMMY_PLANS": {
        "Show orders by status": {
            "query_plan": {
                "resource": "orders",
                "intent": "aggregate",
                "group_by": [{"field": "status"}],
                "metrics": [{"metric": "order_count"}],
                "limit": 100,
            },
            "presentation": {
                "kind": "bar",
                "x": "status",
                "y": "order_count",
            },
        }
    },
}

Ask through the API:

POST /asklens/query/
Content-Type: application/json

{"question": "Show orders by status"}

Successful data responses keep adapter metadata outside one complete core result document:

{
  "run_id": 1,
  "question": "Show orders by status",
  "response_type": "query",
  "plan": {"resource": "orders", "intent": "aggregate", "limit": 100},
  "result": {
    "columns": [
      {"key": "status", "label": "Status", "type": "string", "nullable": false},
      {
        "key": "order_count",
        "label": "Orders",
        "type": "integer",
        "nullable": false
      }
    ],
    "data": [{"status": "paid", "order_count": 12}],
    "row_count": 1,
    "duration_ms": 4,
    "result_metadata": {
      "limit": 100,
      "limit_scope": "groups",
      "truncated": false
    }
  },
  "presentation": {"kind": "bar"}
}

Help questions such as show me example queries return response_type: "capabilities" with exact capabilities and permission-scoped catalog children, plus separate routing and human help objects, without running a database query.

Building a UI

When installed with the api extra, AskLens is API-first. Build your own UI with React, Vue, HTMX, Django templates, a mobile client, or any chart/table library by rendering the returned result.columns and result.data arrays.

The packaged frontend is optional and intended as a dependency-free demo/reference UI. Projects that need product-specific layout, charts, saved queries, or workflows should call the API directly. See Building a custom AskLens UI.

For a concise source-checkout journey that starts with the scoped facility-owner identity, then separates frontend, admin query, view-only audit, safe denial, and reset steps, follow the source demo frontend/admin first run. It uses only synthetic data and deterministic offline help; it is not a supported upgrade path from the historical 0.1.0a1 testing artifact.

Optional packaged frontend

If you want the built-in reference UI, install the api extra and mount both API and frontend URLs:

urlpatterns = [
    path("", include("django_asklens.api.urls")),
    path("", include("django_asklens.frontend.urls")),  # /asklens/ui/
]

Gate the page for selected users with:

DJANGO_ASKLENS = {
    "FRONTEND_PERMISSION_CHECK": "myapp.permissions.can_use_asklens_frontend",
}

API route permissions still apply to every API call. The frontend permission check only controls whether the packaged page can load.

Live providers

The default backend is dummy and makes no network calls. To use an OpenAI-compatible provider:

import os

DJANGO_ASKLENS = {
    "LLM_BACKEND": "openai_compatible",
    "LLM_BASE_URL": "https://api.openai.com/v1",
    "LLM_API_KEY": os.environ["OPENAI_API_KEY"],
    "LLM_MODEL": "gpt-4.1-mini",
    "LLM_TEMPERATURE": 0,
}

Gemini can be used through its OpenAI-compatible endpoint:

DJANGO_ASKLENS = {
    "LLM_BACKEND": "openai_compatible",
    "LLM_BASE_URL": "https://generativelanguage.googleapis.com/v1beta/openai",
    "LLM_API_KEY": os.environ["GEMINI_API_KEY"],
    "LLM_MODEL": "gemini-2.5-flash",
    "LLM_TEMPERATURE": 0,
}

Live provider tests are opt-in and skipped by default. See Provider configuration.

Safety posture

  • Only explicitly registered resources and fields are queryable through the normal validated orchestration paths.
  • Every resource resolves to global or context_scoped. Projects may configure DEFAULT_SCOPE_MODE="context_scoped" once, but global must always be explicit per resource. Context-scoped resources require a trusted scope_provider(request); omission, invalid provider results, and provider failures reject without falling back to the model manager.
  • Sensitive fields are hidden unless explicitly permissioned through the normal validation paths.
  • Plans are bounded before ORM compilation by UTF-8 bytes, filters, selected/order/group/metric terms, relationship depth and unique edges, in/total filter values, and returned rows/groups.
  • List ordering uses semantic resource defaults plus a private unique row identity; grouped queries append group-key tie-breakers, nulls sort last, and truncated is derived by fetching one extra row/group.
  • Filter operators and JSON values are checked against canonical field types before scope resolution; decimals remain strings, explicit enum aliases are server-registered, and Django choices are not auto-exposed.
  • Result columns include type/nullability metadata. Empty aggregates and decimal serialization are deterministic, and unsupported runtime values fail instead of being stringified.
  • Provider and submitted-plan output is untrusted and validated by the normal API, admin, and MCP orchestration before execution.
  • The optional query API rejects unknown top-level keys—including client policy claims—before orchestration, audit, or application-data SQL without reflecting their names, values, or serializer diagnostics.
  • All handled failures from the four AskLens DRF views use one {error, run_id?} envelope with fixed safe messages; statuses and applicable Allow, WWW-Authenticate, and Retry-After headers are preserved. This route-local behavior does not change unrelated host DRF endpoints.
  • Use django_asklens.execution.execute_plan() for Python execution; it revalidates mappings and existing QueryPlan objects for the current request. The compiler and compiled-query executor are internal and are not public exports.
  • AskLens executes read-only Django ORM queries only.
  • AskLens does not execute LLM-generated SQL.
  • AskLens does not create, update, or delete application data; its own optional/default audit sink may write one SemanticQueryRun metadata record per query attempt.
  • Default audit records omit questions, filter values, and complete plans unless AUDIT_INCLUDE_CONTENT=True is explicitly configured; run detail reapplies that current policy even to legacy rows.
  • Run detail is owner-only unless the current user has global asklens.view_semanticqueryrun; is_staff alone is insufficient, inaccessible/missing IDs share an opaque 404, and stored free-form errors are returned only as canonical safe {code, message} metadata or null.
  • Built-in audit writes and run-detail reads may use one optional server-owned AUDIT_DATABASE_ALIAS; clients cannot select it and a configured alias never falls back to default.
  • AskLens does not send database rows, sample values, secrets, credentials, or .env content to providers by default.
  • Query runs are audited; hosts own audit retention, access, redaction, deletion, backups, replicas, and any full-content policy.

Review the security checklist and production checklist before enabling AskLens outside local development.

Alpha scope and safety boundaries

  • APIs may change before a stable release.
  • This alpha is not a production-security certification. Host applications remain responsible for authentication, correct scope-provider policy, database and request timeouts, rate/concurrency limits, read-only database defense where appropriate, and application-specific security testing.
  • AskLens supports read-only list and aggregate questions over explicitly registered resources.
  • Query quality depends on clear resource, field, description, and metric registration.
  • Live provider behavior varies by model and prompt complexity; DummyProvider remains the deterministic default for tests and demos.
  • SQL generation/execution is intentionally out of scope. AskLens uses validated QueryPlan JSON and Django ORM compilation only.
  • Writes and mutations are intentionally out of scope.
  • Server-side saved queries, dashboard builders, and a dedicated help endpoint are not part of the alpha package surface.
  • The packaged frontend is a reference/demo UI; custom product UIs should call the API directly.
  • Read-only replica/database routing is a host-project deployment concern in alpha.
  • Installation metadata remains Django>=5.2,<7.0, but current CI support evidence is deliberately limited to Django 5.2 LTS, 6.0, and 6.1.

Documentation

Development

Use Python 3.12 or newer and uv for local development. The dev dependency group includes DRF so the API integration tests run locally.

uv sync --group dev
uv run pytest
uv run ruff check .
uv run ruff format --check .

The opt-in source-checkout PostgreSQL 18 browser smoke is:

uv run playwright install chromium
bash scripts/reference-demo-smoke.sh

It uses only synthetic data, disables live providers, and is internal draft alpha-candidate evidence—not production/security certification, external validation, or backend-neutral proof. The package remains standards-based and setuptools-backed; uv, Docker, PostgreSQL drivers, and Playwright are contributor/evidence tools, not mandatory runtime dependencies.

Metadata

Release files for django-asklens 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for django-asklens 0.2.0
File Size Uploaded
django_asklens-0.2.0.tar.gz 300.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for django-asklens 0.2.0
File Interpreter ABI Platform
django_asklens-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 440.8 kB

Release files / django_asklens-0.2.0.tar.gz

Download URL django_asklens-0.2.0.tar.gz
Size 300.7 kB
Tags Source
SHA-256 checksum
How to use checksums
11af90303fd2d23123e4caa98c6e6cb660b7b8f32d6fba0c57a4554c8d39e022
BLAKE2b-256 checksum
How to use checksums
73f8676baedbb7fc9a6b9650a1206b2a3ffc592a7e0c619241336c120f4df6d0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.7

Release files / django_asklens-0.2.0-py3-none-any.whl

Download URL django_asklens-0.2.0-py3-none-any.whl
Size 140.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
af0881945d5f5f227332aac8bb13df10d4cf5bd3e54a038acd6545db5640d1d8
BLAKE2b-256 checksum
How to use checksums
2409b277b4cfa5901e151281b95cfbc9f9fa99cd28346af5833fbbc32acf00be
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.7

Release history Release notifications | RSS feed

0.3.1

2 release files

0.3.0

2 release files

This release

0.2.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page