Django AskLens
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
QueryPlanvalidation. - 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+dataJSON 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
globalorcontext_scoped. Projects may configureDEFAULT_SCOPE_MODE="context_scoped"once, butglobalmust always be explicit per resource. Context-scoped resources require a trustedscope_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
truncatedis 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 applicableAllow,WWW-Authenticate, andRetry-Afterheaders 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 existingQueryPlanobjects 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
SemanticQueryRunmetadata record per query attempt. - Default audit records omit questions, filter values, and complete plans unless
AUDIT_INCLUDE_CONTENT=Trueis 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_staffalone is insufficient, inaccessible/missing IDs share an opaque404, and stored free-form errors are returned only as canonical safe{code, message}metadata ornull. - 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 todefault. - AskLens does not send database rows, sample values, secrets, credentials, or
.envcontent 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;
DummyProviderremains 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
- Installation
- Usage guide
- Core Python API
- Custom UI guide
- Registration API
- Provider configuration
- Contributing
- Security policy
- Security checklist
- Production checklist
- Multi-tenant security
- Evaluation fixtures
- Example Django AskLens integration
- Changelog
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)
| File | Size | Uploaded | |
|---|---|---|---|
| django_asklens-0.2.0.tar.gz | 300.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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
|