Django Model Search
django-modelsearch is a Python library that enables indexing Django models with various search backends, including Elasticsearch, OpenSearch, and the database itself (PostgreSQL, MySQL). It provides a unified API for searching models, inspired by Wagtail Search. The current version is 1.2.0, with releases occurring periodically to support Django and backend updates.
Common errors
-
ModuleNotFoundError: No module named 'modelsearch'
cause The `django-modelsearch` library (which is installed as `modelsearch`) is either not installed in the environment, or the Django app has not included 'modelsearch' in its INSTALLED_APPS setting.fixFirst, ensure the package is installed: `pip install modelsearch`. Then, add `'modelsearch'` to your `INSTALLED_APPS` in `settings.py`: `INSTALLED_APPS = [... 'modelsearch', ...]`. -
django.core.exceptions.ImproperlyConfigured: MODELSEARCH_BACKENDS setting is not configured correctly
cause The `MODELSEARCH_BACKENDS` setting is missing or incorrectly defined in your Django project's `settings.py`, which is required when using external search backends like Elasticsearch or OpenSearch.fixAdd the `MODELSEARCH_BACKENDS` dictionary to your `settings.py` with at least a 'default' backend configuration, specifying the `BACKEND` and `URLS` (if applicable), as shown in the documentation. For example: `MODELSEARCH_BACKENDS = {'default': {'BACKEND': 'modelsearch.backends.elasticsearch8', 'URLS': ['http://localhost:9200'], 'INDEX_PREFIX': 'myproject_'}}`. -
Related Field got invalid lookup: icontains
cause This error occurs when you try to use a `SearchField` in your `search_fields` list on a related model field (like a `ForeignKey` or `ManyToManyField`) directly, instead of specifying a text field on the related model itself.fixWhen defining `search_fields`, use double-underscore notation to specify a text-based field on the related model. For example, instead of `index.SearchField('artist')`, use `index.RelatedFields('artist', [index.SearchField('name')])` to search the 'name' field of the 'artist' related model. -
AttributeError: 'Manager' object has no attribute 'search'
cause Your Django model or its manager has not been correctly configured to include the `django-modelsearch`'s search capabilities. This typically means the model does not inherit from `modelsearch.index.Indexed` or its manager is not an instance of `modelsearch.models.SearchQuerySet` or a subclass.fixEnsure your model inherits from `modelsearch.index.Indexed` and that your model's manager is set up to use `SearchQuerySet`. For example: `from modelsearch import index, models` then `class MyModel(index.Indexed, models.Model): ... objects = models.SearchQuerySet.as_manager()`.
Warnings
- breaking Support for OpenSearch 1.x was dropped in version 1.1, which aligns with OpenSearch's End-of-Life for that series. Users on OpenSearch 1.x must upgrade their OpenSearch cluster or use an older version of django-modelsearch (pre-1.1).
- breaking Version 1.0 was a fork from Wagtail's internal search module. If you are migrating from an older Wagtail project that used its built-in search before this fork, direct migration might require adjustments to settings and index definitions, as some internal paths or configurations may have changed.
- deprecated The hidden setting `_WAGTAILSEARCH_FORCE_AUTO_UPDATE` was removed in version 1.1.1. While not a public API, any reliance on this internal setting will cease to function.
- gotcha For PostgreSQL database search and certain ORM features (like `SearchVector`), you must include `django.contrib.postgres` in your `INSTALLED_APPS`.
- gotcha Configuring search backends (`MODELSEARCH_BACKENDS`) correctly is crucial. Misconfigurations can lead to indexing failures or incorrect search results without immediate errors. Pay close attention to backend types, URLs, and index names.
Install
-
pip install django-modelsearch -
pip install "django-modelsearch[elasticsearch]" -
pip install "django-modelsearch[opensearch]"
Imports
- Index
from modelsearch.base import Index
- SearchField
from modelsearch.fields import SearchField
- MatchAll
from modelsearch.query import MatchAll
- ElasticsearchSearchBackend
from modelsearch.backends import ElasticsearchSearchBackend
from modelsearch.backends.elasticsearch import ElasticsearchSearchBackend
Quickstart
import os
from django.db import models
from modelsearch.base import Index
from modelsearch.fields import SearchField
from modelsearch.query import MatchAll
from django.conf import settings
# Minimal Django settings setup for quickstart, typically in settings.py
if not settings.configured:
settings.configure(
INSTALLED_APPS=[
"modelsearch",
"django.contrib.postgres" # For PostgreSQL DB search features
],
MODELSEARCH_BACKENDS={
"default": {
"BACKEND": "modelsearch.backends.database.DatabaseSearchBackend"
# "BACKEND": "modelsearch.backends.elasticsearch.ElasticsearchSearchBackend",
# "URLS": [os.environ.get('ELASTICSEARCH_URL', 'http://localhost:9200')],
# "INDEX": "test_modelsearch",
}
},
DATABASES={
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": ":memory:",
}
},
SECRET_KEY="dummy-secret-key"
)
# Define a Django Model
class Article(models.Model):
title = models.CharField(max_length=255)
body = models.TextField()
class Search(Index):
fields = [
SearchField("title", boost=10),
SearchField("body"),
]
def __str__(self):
return self.title
# Example usage (run after Django setup and migrations)
# This part assumes a database is configured and tables are created.
# For a real application, you'd run `manage.py migrate` and then `manage.py update_index`.
if __name__ == "__main__":
# In a real Django project, you'd import Article and use its manager.
# For this isolated example, we simulate object creation.
# Note: Search index update requires running `manage.py update_index` or calling backend manually.
print("Quickstart will only demonstrate search query structure.")
print("To run fully, ensure Django is setup, migrated, and index updated.")
# Example search (using a dummy queryset for demonstration)
# In a real app, this would be: `Article.objects.search("search term")`
search_query = "example"
# Simulate a search call
class MockSearchManager:
def search(self, query):
print(f"Searching for: {query}")
return ["MockResult1", "MockResult2"]
mock_manager = MockSearchManager()
print(f"\nSearch for '{search_query}':")
results = mock_manager.search(search_query)
print(f"Results: {results}")
print("\nSearch for all with MatchAll():")
results_all = mock_manager.search(MatchAll())
print(f"Results: {results_all}")