123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423 |
- .. _wagtailsearch_searching:
- =========
- Searching
- =========
- .. _wagtailsearch_searching_pages:
- Searching QuerySets
- ===================
- Wagtail search is built on Django's `QuerySet API <https://docs.djangoproject.com/en/stable/ref/models/querysets/>`_. You should be able to search any Django QuerySet provided the model and the fields being filtered on have been added to the search index.
- Searching Pages
- ---------------
- Wagtail provides a shortcut for searching pages: the ``.search()`` ``QuerySet`` method. You can call this on any ``PageQuerySet``. For example:
- .. code-block:: python
- # Search future EventPages
- >>> from wagtail.core.models import EventPage
- >>> EventPage.objects.filter(date__gt=timezone.now()).search("Hello world!")
- All other methods of ``PageQuerySet`` can be used with ``search()``. For example:
- .. code-block:: python
- # Search all live EventPages that are under the events index
- >>> EventPage.objects.live().descendant_of(events_index).search("Event")
- [<EventPage: Event 1>, <EventPage: Event 2>]
- .. note::
- The ``search()`` method will convert your ``QuerySet`` into an instance of one of Wagtail's ``SearchResults`` classes (depending on backend). This means that you must perform filtering before calling ``search()``.
- .. _wagtailsearch_images_documents_custom_models:
- Searching Images, Documents and custom models
- ---------------------------------------------
- Wagtail's document and image models provide a ``search`` method on their QuerySets, just as pages do:
- .. code-block:: python
- >>> from wagtail.images.models import Image
- >>> Image.objects.filter(uploaded_by_user=user).search("Hello")
- [<Image: Hello>, <Image: Hello world!>]
- :ref:`Custom models <wagtailsearch_indexing_models>` can be searched by using the ``search`` method on the search backend directly:
- .. code-block:: python
- >>> from myapp.models import Book
- >>> from wagtail.search.backends import get_search_backend
- # Search books
- >>> s = get_search_backend()
- >>> s.search("Great", Book)
- [<Book: Great Expectations>, <Book: The Great Gatsby>]
- You can also pass a QuerySet into the ``search`` method which allows you to add filters to your search results:
- .. code-block:: python
- >>> from myapp.models import Book
- >>> from wagtail.search.backends import get_search_backend
- # Search books
- >>> s = get_search_backend()
- >>> s.search("Great", Book.objects.filter(published_date__year__lt=1900))
- [<Book: Great Expectations>]
- .. _wagtailsearch_specifying_fields:
- Specifying the fields to search
- -------------------------------
- By default, Wagtail will search all fields that have been indexed using ``index.SearchField``.
- This can be limited to a certain set of fields by using the ``fields`` keyword argument:
- .. code-block:: python
- # Search just the title field
- >>> EventPage.objects.search("Event", fields=["title"])
- [<EventPage: Event 1>, <EventPage: Event 2>]
- .. _wagtailsearch_faceted_search:
- Faceted search
- --------------
- Wagtail supports faceted search which is a kind of filtering based on a taxonomy
- field (such as category or page type).
- The ``.facet(field_name)`` method returns an ``OrderedDict``. The keys are
- the IDs of the related objects that have been referenced by the specified field, and the
- values are the number of references found for each ID. The results are ordered by number
- of references descending.
- For example, to find the most common page types in the search results:
- .. code-block:: python
- >>> Page.objects.search("Test").facet("content_type_id")
- # Note: The keys correspond to the ID of a ContentType object; the values are the
- # number of pages returned for that type
- OrderedDict([
- ('2', 4), # 4 pages have content_type_id == 2
- ('1', 2), # 2 pages have content_type_id == 1
- ])
- Changing search behaviour
- -------------------------
- Search operator
- ^^^^^^^^^^^^^^^
- The search operator specifies how search should behave when the user has typed in multiple search terms. There are two possible values:
- - "or" - The results must match at least one term (default for Elasticsearch)
- - "and" - The results must match all terms (default for database search)
- Both operators have benefits and drawbacks. The "or" operator will return many more results but will likely contain a lot of results that aren't relevant. The "and" operator only returns results that contain all search terms, but require the user to be more precise with their query.
- We recommend using the "or" operator when ordering by relevance and the "and" operator when ordering by anything else (note: the database backend doesn't currently support ordering by relevance).
- Here's an example of using the ``operator`` keyword argument:
- .. code-block:: python
- # The database contains a "Thing" model with the following items:
- # - Hello world
- # - Hello
- # - World
- # Search with the "or" operator
- >>> s = get_search_backend()
- >>> s.search("Hello world", Things, operator="or")
- # All records returned as they all contain either "hello" or "world"
- [<Thing: Hello World>, <Thing: Hello>, <Thing: World>]
- # Search with the "and" operator
- >>> s = get_search_backend()
- >>> s.search("Hello world", Things, operator="and")
- # Only "hello world" returned as that's the only item that contains both terms
- [<Thing: Hello world>]
- For page, image and document models, the ``operator`` keyword argument is also supported on the QuerySet's ``search`` method:
- .. code-block:: python
- >>> Page.objects.search("Hello world", operator="or")
- # All pages containing either "hello" or "world" are returned
- [<Page: Hello World>, <Page: Hello>, <Page: World>]
- Phrase searching
- ^^^^^^^^^^^^^^^^
- Phrase searching is used for finding whole sentence or phrase rather than individual terms.
- The terms must appear together and in the same order.
- For example:
- .. code-block:: python
- >>> from wagtail.search.query import Phrase
- >>> Page.objects.search(Phrase("Hello world"))
- [<Page: Hello World>]
- >>> Page.objects.search(Phrase("World hello"))
- [<Page: World Hello day>]
- If you are looking to implement phrase queries using the double-quote syntax, see :ref:`wagtailsearch_query_string_parsing`.
- .. _wagtailsearch_complex_queries:
- Complex search queries
- ^^^^^^^^^^^^^^^^^^^^^^
- Through the use of search query classes, Wagtail also supports building search queries as Python
- objects which can be wrapped by and combined with other search queries. The following classes are
- available:
- ``PlainText(query_string, operator=None, boost=1.0)``
- This class wraps a string of separate terms. This is the same as searching without query classes.
- It takes a query string, operator and boost.
- For example:
- .. code-block:: python
- >>> from wagtail.search.query import PlainText
- >>> Page.objects.search(PlainText("Hello world"))
- # Multiple plain text queries can be combined. This example will match both "hello world" and "Hello earth"
- >>> Page.objects.search(PlainText("Hello") & (PlainText("world") | PlainText("earth")))
- ``Phrase(query_string)``
- This class wraps a string containing a phrase. See previous section for how this works.
- For example:
- .. code-block:: python
- # This example will match both the phrases "hello world" and "Hello earth"
- >>> Page.objects.search(Phrase("Hello world") | Phrase("Hello earth"))
- ``Boost(query, boost)``
- This class boosts the score of another query.
- For example:
- .. code-block:: python
- >>> from wagtail.search.query import PlainText, Boost
- # This example will match both the phrases "hello world" and "Hello earth" but matches for "hello world" will be ranked higher
- >>> Page.objects.search(Boost(Phrase("Hello world"), 10.0) | Phrase("Hello earth"))
- Note that this isn't supported by the PostgreSQL or database search backends.
- .. _wagtailsearch_query_string_parsing:
- Query string parsing
- ^^^^^^^^^^^^^^^^^^^^
- The previous sections show how to construct a phrase search query manually, but a lot of search engines (Wagtail admin included, try it!)
- support writing phrase queries by wrapping the phrase with double-quotes. In addition to phrases, you might also want to allow users to
- add filters into the query using the colon syntax (``hello world published:yes``).
- These two features can be implemented using the ``parse_query_string`` utility function. This function takes a query string that a user
- typed and returns a query object and dictionary of filters:
- For example:
- .. code-block:: python
- >>> from wagtail.search.utils import parse_query_string
- >>> filters, query = parse_query_string('my query string "this is a phrase" this-is-a:filter', operator='and')
- >>> filters
- {
- 'this-is-a': 'filter',
- }
- >>> query
- And([
- PlainText("my query string", operator='and'),
- Phrase("this is a phrase"),
- ])
- Here's an example of how this function can be used in a search view:
- .. code-block:: python
- from wagtail.search.utils import parse_query_string
- def search(request):
- query_string = request.GET['query']
- # Parse query
- filters, query = parse_query_string(query_string, operator='and')
- # Published filter
- # An example filter that accepts either `published:yes` or `published:no` and filters the pages accordingly
- published_filter = filters.get('published')
- published_filter = published_filter and published_filter.lower()
- if published_filter in ['yes', 'true']:
- pages = pages.filter(live=True)
- elif published_filter in ['no', 'false']:
- pages = pages.filter(live=False)
- # Search
- pages = pages.search(query)
- return render(request, 'search_results.html', {'pages': pages})
- Custom ordering
- ^^^^^^^^^^^^^^^
- By default, search results are ordered by relevance, if the backend supports it. To preserve the QuerySet's existing ordering, the ``order_by_relevance`` keyword argument needs to be set to ``False`` on the ``search()`` method.
- For example:
- .. code-block:: python
- # Get a list of events ordered by date
- >>> EventPage.objects.order_by('date').search("Event", order_by_relevance=False)
- # Events ordered by date
- [<EventPage: Easter>, <EventPage: Halloween>, <EventPage: Christmas>]
- .. _wagtailsearch_annotating_results_with_score:
- Annotating results with score
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- For each matched result, Elasticsearch calculates a "score", which is a number
- that represents how relevant the result is based on the user's query. The
- results are usually ordered based on the score.
- There are some cases where having access to the score is useful (such as
- programmatically combining two queries for different models). You can add the
- score to each result by calling the ``.annotate_score(field)`` method on the
- ``SearchQuerySet``.
- For example:
- .. code-block:: python
- >>> events = EventPage.objects.search("Event").annotate_score("_score")
- >>> for event in events:
- ... print(event.title, event._score)
- ...
- ("Easter", 2.5),
- ("Haloween", 1.7),
- ("Christmas", 1.5),
- Note that the score itself is arbitrary and it is only useful for comparison
- of results for the same query.
- .. _wagtailsearch_frontend_views:
- An example page search view
- ===========================
- Here's an example Django view that could be used to add a "search" page to your site:
- .. code-block:: python
- # views.py
- from django.shortcuts import render
- from wagtail.core.models import Page
- from wagtail.search.models import Query
- def search(request):
- # Search
- search_query = request.GET.get('query', None)
- if search_query:
- search_results = Page.objects.live().search(search_query)
- # Log the query so Wagtail can suggest promoted results
- Query.get(search_query).add_hit()
- else:
- search_results = Page.objects.none()
- # Render template
- return render(request, 'search_results.html', {
- 'search_query': search_query,
- 'search_results': search_results,
- })
- And here's a template to go with it:
- .. code-block:: html
- {% extends "base.html" %}
- {% load wagtailcore_tags %}
- {% block title %}Search{% endblock %}
- {% block content %}
- <form action="{% url 'search' %}" method="get">
- <input type="text" name="query" value="{{ search_query }}">
- <input type="submit" value="Search">
- </form>
- {% if search_results %}
- <ul>
- {% for result in search_results %}
- <li>
- <h4><a href="{% pageurl result %}">{{ result }}</a></h4>
- {% if result.search_description %}
- {{ result.search_description|safe }}
- {% endif %}
- </li>
- {% endfor %}
- </ul>
- {% elif search_query %}
- No results found
- {% else %}
- Please type something into the search box
- {% endif %}
- {% endblock %}
- Promoted search results
- =======================
- "Promoted search results" allow editors to explicitly link relevant content to search terms, so results pages can contain curated content in addition to results from the search engine.
- This functionality is provided by the :mod:`~wagtail.contrib.search_promotions` contrib module.
|