Query language¶
You give your users a real search box – field-scoped terms, quoted phrases,
boolean logic – and search_query_to_q() turns what
they type into a Django Q. Pass that to any
QuerySet and the ORM does the rest: string in,
queryset out. A field map plus that one call covers most sites, so if the
defaults fit you can stop after the Tutorial.
The syntax is Lucene-inspired, not Lucene-compatible – familiar forms mostly
just work. A query the parser cannot read raises
QueryParseError; catch it to fall back to a
plain search, which is exactly what the
admin integration does.
Underneath, a tokenizer lexes the string, parse()
builds an AST, and build_q() compiles it to a Q;
the pipeline walks that chain.
See it work¶
Seed a handful of articles:
title |
status |
author |
created |
|---|---|---|---|
Closed ticket |
closed |
tony |
2023-11-20 |
Draft memo |
draft |
jane |
2024-03-15 |
Launch plan |
open |
tony |
2024-06-01 |
Report Q3 |
open |
mint |
2024-09-01 |
Each search string compiles to a Q, and QuerySet
.filter(q) runs it as SQL:
You type |
It returns |
The SQL Django runs |
|---|---|---|
|
Launch plan, Report Q3 |
|
|
Closed ticket, Launch plan |
|
|
Draft memo, Launch plan, Report Q3 |
|
|
Draft memo, Launch plan, Report Q3 |
|
|
Report Q3 |
|
|
Draft memo, Launch plan, Report Q3 |
|
|
Launch plan |
|
Rows are listed in created order. Every row is checked against a live
Article table in tests/test_query_showcase.py, so the SQL and matches stay
honest. (The SQL is cleaned for reading and rendered on SQLite; str(qs.query)
shows Django’s fuller debug form, and your own database renders the equivalent
lookups – PostgreSQL, for instance, wraps case-insensitive matches in
UPPER(...).)
Here is the Q behind the first row – the whole magic in one call:
>>> from django_search_query import search_query_to_q
>>> from django_search_query.registry import FieldRegistry, FieldSpec
>>> registry = FieldRegistry(specs=(FieldSpec(name="status", kind="enum"),))
>>> q = search_query_to_q(
... "status:open",
... registry=registry,
... field_map={"status": "status"},
... default_fields=("title", "body"),
... )
>>> q
<Q: (AND: ('status__iexact', 'open'))>
Article.objects.filter(q) then returns the matching rows. The other rows
declare author, title, and created in the registry the same way – the
Tutorial builds a registry like that end to end.
Syntax¶
Every row compiles exactly as shown. default_fields is where bare terms and
phrases search; a FieldRegistry decides
which field: names are allowed and how each compiles.
Syntax |
Example |
Compiles to |
|---|---|---|
Bare term |
|
|
Quoted phrase |
|
|
String field |
|
|
Enum field |
|
|
Trailing wildcard |
|
|
Leading wildcard |
|
|
Comparison |
|
|
Inclusive range |
|
|
Exclusive range |
|
|
Existence |
|
|
OR |
|
|
Implicit AND |
|
|
Negation |
|
|
|
|
literal |
Bare value on a |
|
literal |
The last two rows are deliberate, not bugs: ? is a wildcard character to the
highlighter only, and a field’s kind controls formatting, not parsing – a
bare value never becomes a Python date or int before it reaches the ORM.
Next¶
Tutorial – build a
FieldRegistryand run a query end to end.API reference –
search_query_to_q(),parse(),build_q(), and the field registry.How-to guides – field maps, restricting fields, wildcards and ranges, catching
QueryParseError.Admin integration – put this in the Django admin changelist.