Copy the above template to yourproject/yourapp/templates/admin/base_site.html
and ensure your app is registered after Django's admin app.
The index page of your admin will now include a search box in the navbar. Typing will filter app names and model names immediately, pressing escape will clear the filter.
Last active
February 22, 2024 12:51
-
-
Save cb109/bc17869ff73c7d2804ae3909264012d7 to your computer and use it in GitHub Desktop.
Django Admin custom clientside Search for Index (tested with Django 3.2)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{% extends "admin/base_site.html" %} | |
{% block usertools %} | |
{% url 'admin:index' as admin_index_url %} | |
{% if request.get_full_path == admin_index_url %} | |
<input | |
type="text" | |
id="admin_base_search" | |
placeholder="Search..." | |
autofocus | |
style="margin-right: auto" | |
onkeydown="onSearchInputKeyPressed(event)" | |
> | |
<script> | |
/** | |
* Implement custom clientside search for app and model names. | |
*/ | |
const APP_CAPTION_SELECTOR = 'div[class^="app-"] caption' | |
const MODEL_ANCHOR_SELECTOR = 'tr[class^="model-"] th a'; | |
const searchInput = document.getElementById('admin_base_search'); | |
const debounce = (callback, wait) => { | |
let timeoutId = null; | |
return (...args) => { | |
window.clearTimeout(timeoutId); | |
timeoutId = window.setTimeout(() => { | |
callback(...args); | |
}, wait); | |
}; | |
} | |
const debouncedToggleModelVisibilityInAdmin = debounce( | |
toggleModelVisibilityInAdmin, 50 | |
); | |
function onSearchInputKeyPressed(event) { | |
if (event.key == 'Escape') { | |
searchInput.value = ''; | |
} | |
debouncedToggleModelVisibilityInAdmin(); | |
} | |
function toggleModelVisibilityInAdmin() { | |
const searchText = searchInput.value.toLowerCase(); | |
// Models | |
const modelAnchors = document.querySelectorAll(MODEL_ANCHOR_SELECTOR); | |
for (const anchor of modelAnchors) { | |
if ( | |
anchor.innerText.toLowerCase().includes(searchText) || | |
anchor.href.toLowerCase().includes(searchText.replace(' ', '')) | |
) { | |
anchor.parentElement.parentElement.style.display = ''; | |
} | |
else { | |
anchor.parentElement.parentElement.style.display = 'none'; | |
} | |
} | |
// Apps: Hide empty apps also. | |
const appCaptions = document.querySelectorAll(APP_CAPTION_SELECTOR); | |
for (const appCaption of appCaptions) { | |
console.log('appCaption', appCaption.innerText); | |
const visibleModels = [ | |
...appCaption | |
.parentElement | |
.parentElement | |
.querySelectorAll(MODEL_ANCHOR_SELECTOR) | |
] | |
.filter(el => el.parentElement.parentElement.style.display !== 'none'); | |
if (visibleModels.length == 0) { | |
appCaption.parentElement.parentElement.style.display = 'none'; | |
} | |
else { | |
appCaption.parentElement.parentElement.style.display = ''; | |
} | |
} | |
} | |
</script> | |
{% endif %} | |
{{ block.super }} | |
{% endblock %} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment