djangoql
Advanced search language for the Django admin, with auto-completion, joins, and logical operators.
Repository Health
Technical Analysis
DjangoQL replaces the plain-text Django admin search box with a small, Python-flavored query language. Instead of a single search string matched against a fixed list of fields, staff users write expressions like name = "foo" and author.email = "em@il" and written > "2017-01-30" — referencing model fields exactly as they’d appear in Python code, following relations with dot notation, and combining conditions with and/or and parentheses.
The library ships a browser-based auto-completion widget (built from a shared PLY lexer/grammar exposed to JavaScript) that suggests field names and value choices as you type, plus a schema layer (DjangoQLSchema) that lets developers restrict which models and fields are searchable, add computed/annotated fields, and customize how each field’s value is looked up. It works as a drop-in DjangoQLSearchMixin for any ModelAdmin, or as a .djangoql() queryset method for use outside the admin.
What You Get
- A drop-in
DjangoQLSearchMixinthat replacesModelAdminsearch with a DjangoQL-powered search bar, optionally toggleable alongside the standardsearch_fieldssearch - A JavaScript auto-completion widget that suggests field names, relations, and value choices live as the query is typed, backed by introspection and suggestion endpoints
- A
DjangoQLSchemaclass for scoping which models/fields are searchable, adding custom fields backed by annotations or fully custom ORM lookups, and controlling suggestion behavior per field - A
.djangoql(query)queryset method (viaDjangoQLQuerySet/apply_search) for running DjangoQL searches anywhere, not just in the admin - Typed field classes (
IntField,FloatField,StrField,BoolField,DateField,DateTimeField,RelationField) that validate query values against the field’s Django type before building aQobject
Common Use Cases
- Letting non-technical admin staff filter large tables by multiple fields and relations without needing a custom admin filter for every combination
- Searching across foreign-key and many-to-many relations (e.g.
author.last_nameorgroups.name) directly from the admin search bar - Exposing computed/annotated queryset fields (e.g. a
groups_countannotation) as first-class searchable fields via a custom schema - Running the same advanced-search syntax outside the Django admin, in any view or management command, via the
.djangoql()queryset method
Under The Hood
Architecture
The codebase is organized as four cooperating layers: a PLY-based DjangoQLLexer/DjangoQLParser (djangoql/lexer.py, djangoql/parser.py) that turns a query string into an AST of Name/Comparison/Logical/Const/List nodes (djangoql/ast.py); a DjangoQLSchema (djangoql/schema.py) that recursively introspects a Django model’s relation graph into typed DjangoQLField instances and validates the AST against it; a queryset.py module that walks the validated AST and folds it into a Django Q object via build_filter, exposed as apply_search() and the DjangoQLQuerySet.djangoql() mixin method; and an admin.py integration layer (DjangoQLSearchMixin, DjangoQLChangeList) that wires the whole pipeline into ModelAdmin.get_search_results(), adds introspect/suggestions admin URLs, and falls back gracefully to Django’s error messages on FieldError/ValidationError/Postgres inet-comparison failures. A parallel completion-widget/ JS package (webpack-built) mirrors the grammar client-side to drive live auto-completion against those same introspect/suggestion endpoints.
Tech Stack
The Python side depends on a single runtime package, ply>=3.8, for lexing/parsing, and integrates with Django via standard ModelAdmin/QuerySet/Q APIs with explicit compatibility shims (compat.py, try/except imports for re_path vs url, django.urls vs django.core.urlresolvers) spanning Django 1.8 through 6.0 and Python 2.7 through 3.14. The frontend widget is built with Babel (.babelrc) and Webpack (webpack.config.js), packaged and versioned independently via package.json/yarn.lock, and the compiled JS/CSS ship as Django static assets under djangoql/static/djangoql/.
Code Quality
The test_project/core/tests/ suite covers the lexer, parser, AST, schema, queryset filtering, and admin integration in separate files (roughly 700 lines of tests), run against a Django test project via manage.py test core.tests. CI (.github/workflows/tests.yaml) runs this suite across an extensive Django/Python version matrix (Django 1.8 through 6.0 against matching Python 2.7–3.14 releases) plus a flake8 + isort lint gate before tests run, with setup.cfg pinning an 80-character line length and explicit import-ordering rules. Error handling is deliberate rather than swallowed: custom exception types (DjangoQLError, DjangoQLLexerError, DjangoQLParserError, DjangoQLSchemaError) carry line/column context, and the admin mixin explicitly catches and surfaces FieldError/ValidationError/DataError as user-facing admin messages instead of raising a 500.
What Makes It Unique
Rather than bolting a search box onto Django admin, DjangoQL builds an actual small language — with its own lexer, grammar, and typed schema-validation pass — purpose-fit to Django’s model/relation graph, so a query like published = False or date_published = None is validated against real field types before it ever reaches the database. Extension points (get_lookup_name, get_lookup_value, a fully overridable get_lookup) let developers redefine what a field name means at the ORM level (custom annotations, computed lookups, cross-field logic) without forking the parser, which is a more structured escape hatch than most ad hoc admin search implementations offer.