django-realtime-search
Filters Django admin changelist pages as you type in the search box. You don't need to press Enter or wait for a page reload.
You don't change any of your existing admins. Every search fetches the normal changelist URL, so your search_fields, get_search_results(), list filters, ordering, pagination and permissions behave exactly as they do today. The script then swaps the new results into the page.
Supports Django 3.2 – 4.2 and Python 3.8+.
Installation
pip install django-realtime-search
Add it to INSTALLED_APPS before django.contrib.admin:
INSTALLED_APPS = [
"realtime_search",
"django.contrib.admin",
...
]
That's it. Every admin changelist with search_fields now searches as you type.
Configuration
Optional, in settings.py (defaults shown):
REALTIME_SEARCH = {
"ENABLED": True, # False = only ModelAdmins that opt in get realtime search
"DEBOUNCE_MS": 300, # wait this long after the last keystroke before searching
"MIN_CHARS": 1, # minimum characters before searching (clearing always searches)
}
Per-ModelAdmin overrides:
class BookAdmin(admin.ModelAdmin):
search_fields = ["title", "author__name"]
realtime_search = False # opt out
# realtime_search = True # opt in (when ENABLED is False)
# realtime_search = {"DEBOUNCE_MS": 150} # opt in and override settings
Typed filters (key:value in the search box)
Add SearchFiltersMixin to a ModelAdmin and list the filters people can type:
from realtime_search.admin import SearchFiltersMixin
@admin.register(Book)
class BookAdmin(SearchFiltersMixin, admin.ModelAdmin):
search_fields = ["title"]
search_filters = {
"author": "author__name", # key typed in the box -> field path
"in_print": "in_print",
"published": "published",
"year": "published__year", # explicit lookups/transforms are fine
"format": "format", # a field with choices
}
# or a list, using the field path as the key: search_filters = ["in_print", "format"]
Users can then type things like:
parable in_print:no
author:butler year:>=1990
author:"le guin" -format:paperback
| Syntax | Meaning |
|---|---|
key:value |
Text fields: contains (case-insensitive). Other fields: equals. |
key:"two words" |
Quoted value |
key:=value |
Exact (case-insensitive for text) |
key:^value |
Starts with (text fields) |
key:>v key:>=v key:<v key:<=v |
Comparisons: numbers, dates, transforms like __year |
-key:value |
Exclude matches |
- Booleans accept
yes/no,true/false,1/0,on/off. - Choice fields match the stored value or the label, and a prefix of the label also matches (
format:hardfinds "Hardcover"). - Dates: a 4-digit value means the year, so
published:1993andpublished:>1990work. - Combining: filters AND together, with each other and with any remaining free-text words, which go through
search_fieldsas usual. - Not recognised: keys that aren't listed are treated as search text, so
Re:Codedstill searches normally. - While typing: a filter with no value yet (
author:) is ignored, and a value that can't match (year:abc) gives zero results rather than an error.
With realtime search on, the allowed keys appear as clickable hints under the search box. Clicking one adds key: to the search.
Only listed keys can be filtered on, and bad field paths are reported by manage.py check. The mixin also works without the realtime JavaScript: the filters apply when the search is submitted.
Behaviour
- Preserves filters and ordering. Any active list filters, date hierarchy and sort order stay applied. A new search starts back at page 1.
- Updates the URL with
history.replaceState, so refreshing the page or sharing the link keeps the search. - Enter searches right away. Escape clears the search.
- Stale responses are dropped. If you keep typing, in-flight requests are cancelled, so results never arrive out of order.
- Admin actions keep working. The select-all checkbox and the "N of M selected" counter are re-bound after each update.
- Protects unsaved
list_editableedits. Once a list_editable field has been changed, live updates pause, and pressing Enter does a normal page submit. - Fails safe. If a response isn't a changelist (an error, or a redirect to the login page because the session expired), the browser goes to the URL normally.
- Accessible. The result count is announced to screen readers through an
aria-liveregion.
After each update, a realtime-search:updated event is dispatched on document (event.detail = {query, url}). Use it to re-initialise any custom JavaScript on the result rows.
How it hooks in
The app ships an admin/change_list.html that extends the next admin/change_list.html on the template path (Django supports recursive template extends). It only adds a script and stylesheet to {% block extrahead %}. That means it coexists with:
- your own project-level
admin/change_list.htmloverrides - custom
ModelAdmin.change_list_templates that extendadmin/change_list.html - third-party apps that do the same (e.g. django-import-export)
If a template replaces {% block extrahead %} without calling {{ block.super }}, realtime search won't load on that page.
Limitations
- Each search is a full changelist request on the server. On very large tables with expensive
search_fields(e.g. many__icontainsjoins), raiseDEBOUNCE_MSorMIN_CHARS. - Widgets that need JavaScript initialisation inside
list_editablerows, such as calendar shortcuts on date fields, aren't re-initialised after an update.
Development
pip install -e ".[test]"
playwright install chromium # optional: enables the browser tests
python runtests.py # browser tests are skipped if Playwright is missing
tox # full Django/Python matrix
To try it by hand:
export REALTIME_SEARCH_DB=demo.sqlite3
django-admin migrate --run-syncdb --settings=tests.settings --pythonpath=.
django-admin seed_demo --count 2000 --settings=tests.settings --pythonpath=.
django-admin runserver --settings=tests.settings --pythonpath=.
# open http://127.0.0.1:8000/admin/testapp/book/ (admin / admin)
Metadata
Release files for django-realtime-search 0.1.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_realtime_search-0.1.0.tar.gz | 17.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| django_realtime_search-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 32.4 kB
Release files / django_realtime_search-0.1.0.tar.gz
| Download URL | django_realtime_search-0.1.0.tar.gz |
|---|---|
| Size | 17.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d7f481938ce5fd7a947d97def54620da127c7ced7ff41aba9d9d37e2472f5c15
|
|
BLAKE2b-256 checksum How to use checksums |
32d6c78da5633c1088a1445584e57be2adbe9b15a14a888f335a09f6d66732d2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.
Transparency logRelease files / django_realtime_search-0.1.0-py3-none-any.whl
| Download URL | django_realtime_search-0.1.0-py3-none-any.whl |
|---|---|
| Size | 15.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
92196cc80d03422d0fcabcd2c8f4ed62686d25d3281add48f5bed3173708b6c6
|
|
BLAKE2b-256 checksum How to use checksums |
29f166a97c96a7dfd792530c70c78e59e55ef1990a502205c3898a60af730528
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.
Transparency log