# django-admin-site-search
[![Test](https://github.com/ahmedaljawahiry/django-admin-site-search/actions/workflows/test.yaml/badge.svg)](https://github.com/ahmedaljawahiry/django-admin-site-search/actions/workflows/test.yaml)
[![Lint](https://github.com/ahmedaljawahiry/django-admin-site-search/actions/workflows/lint.yaml/badge.svg)](https://github.com/ahmedaljawahiry/django-admin-site-search/actions/workflows/lint.yaml)
[![PyPI](https://github.com/ahmedaljawahiry/django-admin-site-search/actions/workflows/pypi.yaml/badge.svg)](https://github.com/ahmedaljawahiry/django-admin-site-search/actions/workflows/pypi.yaml)
[![Coverage Python](https://img.shields.io/badge/coverage%20(.py)-100%25-brightgreen.svg)](https://github.com/ahmedaljawahiry/django-admin-site-search/actions/workflows/test.yaml)
[![Coverage Javascript](https://img.shields.io/badge/coverage%20(.js)-TBD-green.svg)](https://github.com/ahmedaljawahiry/django-admin-site-search/actions/workflows/test.yaml)
[![Code style](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
[![Pre-Commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logoColor=white)](https://github.com/ahmedaljawahiry/django-admin-site-search/blob/main/.pre-commit-config.yaml)
[![PyPI version](https://img.shields.io/pypi/v/django-admin-site-search.svg)](https://pypi.org/project/django-admin-site-search/)
[![Downloads](https://static.pepy.tech/badge/django-admin-site-search)](https://pepy.tech/project/django-admin-site-search)
[![PyPI license](https://img.shields.io/pypi/l/django-admin-site-search.svg)](https://github.com/ahmedaljawahiry/django-admin-site-search/blob/main/LICENSE)
A global/site search modal for the Django admin.
<img src="https://raw.githubusercontent.com/ahmedaljawahiry/django-admin-site-search/main/images/demo.gif" width="100%" alt="Preview/demo GIF" />
## Features
- 🎩 Works out-of-the-box, with minimal config.
- 🔎 Search performed on:
- App labels.
- Model labels and field attributes.
- Model instances, with two options for a search method:
1. `model_char_fields` (_default_): All `CharField` (and subclass) values, with `__icontains`.
2. `admin_search_fields`: Invoke each ModelAdmin's
[get_search_results(...)](https://docs.djangoproject.com/en/5.0/ref/contrib/admin/#django.contrib.admin.ModelAdmin.get_search_results) method.
- 🔒 Built-in auth: users can only search apps and models that they have permission to view.
- âš¡ Results appear on-type, with throttling/debouncing to avoid excessive requests.
- 🎹 Keyboard navigation (cmd+k, up/down, enter).
- ✨ Responsive, and supports dark/light mode.
- Django's built-in CSS vars are used to match your admin theme.
## Requirements
- Python 3.7 - 3.12.
- Django 3.2 - 5.1.
## Setup
### 1. Install
1. Install with your package manager, e.g. `pip install django-admin-site-search`.
2. Add `admin_site_search` to your `INSTALLED_APPS` setting.
### 2. Add View
1. If you haven't already, [override/extend the default AdminSite](https://docs.djangoproject.com/en/4.1/ref/contrib/admin/#overriding-the-default-admin-site).
2. Add the `AdminSiteSearchView` to your AdminSite:
```python
from django.contrib import admin
from admin_site_search.views import AdminSiteSearchView
class MyAdminSite(AdminSiteSearchView, admin.AdminSite):
...
```
### 3. Add Templates
1. If you haven't already, create `admin/base_site.html` in your `templates/` directory.
- Note: if your `templates/` directory is inside of an app, then that app must appear in `INSTALLED_APPS` _before_ your custom admin app.
2. Include the `admin_site_search` templates:
```html
{% extends "admin/base_site.html" %}
{% block extrahead %}
{% include 'admin_site_search/head.html' %}
{{ block.super }}
{% endblock %}
{% block footer %}
{{ block.super }}
{% include 'admin_site_search/modal.html' %}
{% endblock %}
{% block usertools %}
{% include 'admin_site_search/button.html' %}
{{ block.super }}
{% endblock %}
```
#### Notes
- Along with styles, `admin_site_search/head.html` loads [Alpine JS](https://alpinejs.dev).
- This is bundled into `/static/`, to avoid external dependencies.
- The placement of `modal.html` and `button.html` are not strict, though the former would ideally be in a top-level
position.
- Django 4.x exposes `{% block header %}` - this is preferable to `footer`.
## Customisation
### Class attributes
```python
class MyAdminSite(AdminSiteSearchView, admin.AdminSite):
# Sets the last part of the search route (`<admin_path>/search/`).
site_search_path: str = "search/"
# Set the search method/behaviour.
site_search_method: Literal["model_char_fields", "admin_search_fields"] = "model_char_fields"
```
### Methods
```python
def match_app(
self, request, query: str, name: str
) -> bool:
"""DEFAULT: case-insensitive match the app name"""
def match_model(
self, request, query: str, name: str, object_name: str, fields: List[Field]
) -> bool:
"""DEFAULT: case-insensitive match the model and field attributes"""
def match_objects(
self, request, query: str, model_class: Model, model_fields: List[Field]
) -> QuerySet:
"""DEFAULT: Returns the QuerySet after performing an OR filter across all Char fields in the model."""
def filter_field(
self, request, query: str, field: Field
) -> Optional[Q]:
"""DEFAULT: Returns a Q 'icontains' filter for Char fields, otherwise None
Note: this method is only invoked if model_char_fields is the site_search_method."""
def get_model_queryset(
self, request, model_class: Model, model_admin: Optional[ModelAdmin]
) -> QuerySet:
"""DEFAULT: Returns the model class' .objects.all() queryset."""
def get_model_class(
self, request, app_label: str, model_dict: dict
) -> Optional[Model]:
"""DEFAULT: Retrieve the model class from the dict created by admin.AdminSite"""
```
#### Example
**Add `TextField` results to search.**
```python
from django.contrib import admin
from django.db.models import Q, Field, TextField
from admin_site_search.views import AdminSiteSearchView
class MyAdminSite(AdminSiteSearchView, admin.AdminSite):
site_search_method: "model_char_fields"
def filter_field(self, request, query: str, field: Field) -> Optional[Q]:
"""Extends super() to add TextField support to site search"""
if isinstance(field, TextField):
return Q(**{f"{field.name}__icontains": query})
return super().filter_field(query, field)
```
Note that this isn't done by default for performance reasons: `__icontains` on a
large number of text entries is suboptimal.
## Screenshots
<img src="https://raw.githubusercontent.com/ahmedaljawahiry/django-admin-site-search/main/images/desktop-light-open.png" width="100%" alt="Desktop, light theme, modal open" />
<p>
<img src="https://raw.githubusercontent.com/ahmedaljawahiry/django-admin-site-search/main/images/mobile-light-closed.png" width="45%" alt="Mobile, light theme, modal closed" />
<img src="https://raw.githubusercontent.com/ahmedaljawahiry/django-admin-site-search/main/images/mobile-dark-open.png" width="45%" alt="Mobile, dark theme, modal open" />
</p>
Raw data
{
"_id": null,
"home_page": "https://github.com/ahmedaljawahiry/django-admin-site-search/",
"name": "django-admin-site-search",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.7",
"maintainer_email": null,
"keywords": "Django",
"author": "Ahmed Al-Jawahiry",
"author_email": "ahmedaljawahiry@gmail.com",
"download_url": "https://files.pythonhosted.org/packages/ee/6c/3c9a7e3c63e6dd5ed645ec499899c39d4914627f2e8638e295cbb89a0aa3/django_admin_site_search-1.1.0.tar.gz",
"platform": null,
"description": "# django-admin-site-search\n\n[![Test](https://github.com/ahmedaljawahiry/django-admin-site-search/actions/workflows/test.yaml/badge.svg)](https://github.com/ahmedaljawahiry/django-admin-site-search/actions/workflows/test.yaml)\n[![Lint](https://github.com/ahmedaljawahiry/django-admin-site-search/actions/workflows/lint.yaml/badge.svg)](https://github.com/ahmedaljawahiry/django-admin-site-search/actions/workflows/lint.yaml)\n[![PyPI](https://github.com/ahmedaljawahiry/django-admin-site-search/actions/workflows/pypi.yaml/badge.svg)](https://github.com/ahmedaljawahiry/django-admin-site-search/actions/workflows/pypi.yaml)\n[![Coverage Python](https://img.shields.io/badge/coverage%20(.py)-100%25-brightgreen.svg)](https://github.com/ahmedaljawahiry/django-admin-site-search/actions/workflows/test.yaml)\n[![Coverage Javascript](https://img.shields.io/badge/coverage%20(.js)-TBD-green.svg)](https://github.com/ahmedaljawahiry/django-admin-site-search/actions/workflows/test.yaml)\n[![Code style](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)\n[![Pre-Commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logoColor=white)](https://github.com/ahmedaljawahiry/django-admin-site-search/blob/main/.pre-commit-config.yaml)\n[![PyPI version](https://img.shields.io/pypi/v/django-admin-site-search.svg)](https://pypi.org/project/django-admin-site-search/)\n[![Downloads](https://static.pepy.tech/badge/django-admin-site-search)](https://pepy.tech/project/django-admin-site-search)\n[![PyPI license](https://img.shields.io/pypi/l/django-admin-site-search.svg)](https://github.com/ahmedaljawahiry/django-admin-site-search/blob/main/LICENSE)\n\nA global/site search modal for the Django admin.\n\n<img src=\"https://raw.githubusercontent.com/ahmedaljawahiry/django-admin-site-search/main/images/demo.gif\" width=\"100%\" alt=\"Preview/demo GIF\" />\n\n## Features\n\n- \ud83c\udfa9 Works out-of-the-box, with minimal config.\n- \ud83d\udd0e Search performed on:\n - App labels.\n - Model labels and field attributes.\n - Model instances, with two options for a search method:\n 1. `model_char_fields` (_default_): All `CharField` (and subclass) values, with `__icontains`.\n 2. `admin_search_fields`: Invoke each ModelAdmin's\n[get_search_results(...)](https://docs.djangoproject.com/en/5.0/ref/contrib/admin/#django.contrib.admin.ModelAdmin.get_search_results) method.\n- \ud83d\udd12 Built-in auth: users can only search apps and models that they have permission to view.\n- \u26a1 Results appear on-type, with throttling/debouncing to avoid excessive requests.\n- \ud83c\udfb9 Keyboard navigation (cmd+k, up/down, enter).\n- \u2728 Responsive, and supports dark/light mode.\n - Django's built-in CSS vars are used to match your admin theme.\n\n## Requirements\n\n- Python 3.7 - 3.12.\n- Django 3.2 - 5.1.\n\n## Setup\n\n### 1. Install\n\n1. Install with your package manager, e.g. `pip install django-admin-site-search`.\n2. Add `admin_site_search` to your `INSTALLED_APPS` setting.\n\n### 2. Add View\n\n1. If you haven't already, [override/extend the default AdminSite](https://docs.djangoproject.com/en/4.1/ref/contrib/admin/#overriding-the-default-admin-site).\n2. Add the `AdminSiteSearchView` to your AdminSite:\n\n```python\nfrom django.contrib import admin\nfrom admin_site_search.views import AdminSiteSearchView\n\nclass MyAdminSite(AdminSiteSearchView, admin.AdminSite):\n ...\n```\n\n\n### 3. Add Templates\n\n1. If you haven't already, create `admin/base_site.html` in your `templates/` directory.\n - Note: if your `templates/` directory is inside of an app, then that app must appear in `INSTALLED_APPS` _before_ your custom admin app.\n2. Include the `admin_site_search` templates:\n```html\n{% extends \"admin/base_site.html\" %}\n\n{% block extrahead %}\n {% include 'admin_site_search/head.html' %}\n {{ block.super }}\n{% endblock %}\n\n{% block footer %}\n {{ block.super }}\n {% include 'admin_site_search/modal.html' %}\n{% endblock %}\n\n{% block usertools %}\n {% include 'admin_site_search/button.html' %}\n {{ block.super }}\n{% endblock %}\n```\n\n#### Notes\n\n- Along with styles, `admin_site_search/head.html` loads [Alpine JS](https://alpinejs.dev). \n - This is bundled into `/static/`, to avoid external dependencies.\n- The placement of `modal.html` and `button.html` are not strict, though the former would ideally be in a top-level\nposition. \n - Django 4.x exposes `{% block header %}` - this is preferable to `footer`.\n\n## Customisation\n\n### Class attributes\n\n```python\nclass MyAdminSite(AdminSiteSearchView, admin.AdminSite):\n \n # Sets the last part of the search route (`<admin_path>/search/`).\n site_search_path: str = \"search/\"\n # Set the search method/behaviour.\n site_search_method: Literal[\"model_char_fields\", \"admin_search_fields\"] = \"model_char_fields\" \n```\n\n### Methods\n\n```python \ndef match_app(\n self, request, query: str, name: str\n) -> bool:\n \"\"\"DEFAULT: case-insensitive match the app name\"\"\"\n\ndef match_model(\n self, request, query: str, name: str, object_name: str, fields: List[Field]\n) -> bool:\n \"\"\"DEFAULT: case-insensitive match the model and field attributes\"\"\"\n\ndef match_objects(\n self, request, query: str, model_class: Model, model_fields: List[Field]\n) -> QuerySet:\n \"\"\"DEFAULT: Returns the QuerySet after performing an OR filter across all Char fields in the model.\"\"\"\n\ndef filter_field(\n self, request, query: str, field: Field\n) -> Optional[Q]:\n \"\"\"DEFAULT: Returns a Q 'icontains' filter for Char fields, otherwise None\n \n Note: this method is only invoked if model_char_fields is the site_search_method.\"\"\"\n\ndef get_model_queryset(\n self, request, model_class: Model, model_admin: Optional[ModelAdmin]\n) -> QuerySet:\n \"\"\"DEFAULT: Returns the model class' .objects.all() queryset.\"\"\"\n\ndef get_model_class(\n self, request, app_label: str, model_dict: dict\n) -> Optional[Model]:\n \"\"\"DEFAULT: Retrieve the model class from the dict created by admin.AdminSite\"\"\"\n```\n\n#### Example\n\n**Add `TextField` results to search.**\n\n```python\nfrom django.contrib import admin\nfrom django.db.models import Q, Field, TextField\nfrom admin_site_search.views import AdminSiteSearchView\n\n\nclass MyAdminSite(AdminSiteSearchView, admin.AdminSite):\n \n site_search_method: \"model_char_fields\" \n \n def filter_field(self, request, query: str, field: Field) -> Optional[Q]:\n \"\"\"Extends super() to add TextField support to site search\"\"\"\n if isinstance(field, TextField):\n return Q(**{f\"{field.name}__icontains\": query})\n return super().filter_field(query, field)\n```\n\nNote that this isn't done by default for performance reasons: `__icontains` on a \nlarge number of text entries is suboptimal.\n\n\n## Screenshots\n<img src=\"https://raw.githubusercontent.com/ahmedaljawahiry/django-admin-site-search/main/images/desktop-light-open.png\" width=\"100%\" alt=\"Desktop, light theme, modal open\" />\n<p>\n <img src=\"https://raw.githubusercontent.com/ahmedaljawahiry/django-admin-site-search/main/images/mobile-light-closed.png\" width=\"45%\" alt=\"Mobile, light theme, modal closed\" />\n <img src=\"https://raw.githubusercontent.com/ahmedaljawahiry/django-admin-site-search/main/images/mobile-dark-open.png\" width=\"45%\" alt=\"Mobile, dark theme, modal open\" /> \n</p>\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "A search (cmd+k) modal, for the Django admin UI, that searches your entire site.",
"version": "1.1.0",
"project_urls": {
"Homepage": "https://github.com/ahmedaljawahiry/django-admin-site-search/",
"Maintainer": "https://ahmedaljawahiry.com"
},
"split_keywords": [
"django"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "9df26ab1d366e52b250210056656022fb329c94fb1aa3457b80a1f32eb47d1f1",
"md5": "f473bfe5f8d3ffd5f99ba1219e7dee56",
"sha256": "fee2318007f87ca2e996d10ead82aa11bdb1858caa7aa23758a164bcc2b0eab3"
},
"downloads": -1,
"filename": "django_admin_site_search-1.1.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "f473bfe5f8d3ffd5f99ba1219e7dee56",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.7",
"size": 36294,
"upload_time": "2024-11-16T16:04:38",
"upload_time_iso_8601": "2024-11-16T16:04:38.517608Z",
"url": "https://files.pythonhosted.org/packages/9d/f2/6ab1d366e52b250210056656022fb329c94fb1aa3457b80a1f32eb47d1f1/django_admin_site_search-1.1.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "ee6c3c9a7e3c63e6dd5ed645ec499899c39d4914627f2e8638e295cbb89a0aa3",
"md5": "a1f61c08b4e2203051c763b7512bf9e3",
"sha256": "86bd0c2a132ae0db353c29eb3eebe9ca92e9208db09f43ee99b30aaba03a4cf4"
},
"downloads": -1,
"filename": "django_admin_site_search-1.1.0.tar.gz",
"has_sig": false,
"md5_digest": "a1f61c08b4e2203051c763b7512bf9e3",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.7",
"size": 35321,
"upload_time": "2024-11-16T16:04:40",
"upload_time_iso_8601": "2024-11-16T16:04:40.119977Z",
"url": "https://files.pythonhosted.org/packages/ee/6c/3c9a7e3c63e6dd5ed645ec499899c39d4914627f2e8638e295cbb89a0aa3/django_admin_site_search-1.1.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-11-16 16:04:40",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "ahmedaljawahiry",
"github_project": "django-admin-site-search",
"travis_ci": false,
"coveralls": true,
"github_actions": true,
"tox": true,
"lcname": "django-admin-site-search"
}