Browse Source

documentation migrate rest of topics/search/** to markdown

LB Johnston 2 years ago
parent
commit
4e7accda55
3 changed files with 414 additions and 482 deletions
  1. 141 165
      docs/topics/search/backends.md
  2. 18 25
      docs/topics/search/index.md
  3. 255 292
      docs/topics/search/searching.md

+ 141 - 165
docs/topics/search/backends.md

@@ -1,205 +1,181 @@
+(wagtailsearch_backends)=
 
-.. _wagtailsearch_backends:
-
-========
-Backends
-========
+# Backends
 
 Wagtailsearch has support for multiple backends, giving you the choice between using the database for search or an external service such as Elasticsearch.
 
-You can configure which backend to use with the ``WAGTAILSEARCH_BACKENDS`` setting:
-
-.. code-block:: python
+You can configure which backend to use with the `WAGTAILSEARCH_BACKENDS` setting:
 
-  WAGTAILSEARCH_BACKENDS = {
-      'default': {
-          'BACKEND': 'wagtail.search.backends.database',
-      }
-  }
+```python
+WAGTAILSEARCH_BACKENDS = {
+    'default': {
+        'BACKEND': 'wagtail.search.backends.database',
+    }
+}
+```
 
+(wagtailsearch_backends_auto_update)=
 
-.. _wagtailsearch_backends_auto_update:
-
-``AUTO_UPDATE``
-===============
+## `AUTO_UPDATE`
 
 By default, Wagtail will automatically keep all indexes up to date. This could impact performance when editing content, especially if your index is hosted on an external service.
 
-The ``AUTO_UPDATE`` setting allows you to disable this on a per-index basis:
-
-.. code-block:: python
+The `AUTO_UPDATE` setting allows you to disable this on a per-index basis:
 
-  WAGTAILSEARCH_BACKENDS = {
-      'default': {
-          'BACKEND': ...,
-          'AUTO_UPDATE': False,
-      }
-  }
+```python
+WAGTAILSEARCH_BACKENDS = {
+    'default': {
+        'BACKEND': ...,
+        'AUTO_UPDATE': False,
+    }
+}
+```
 
-If you have disabled auto update, you must run the :ref:`update_index` command on a regular basis to keep the index in sync with the database.
+If you have disabled auto update, you must run the [](update_index) command on a regular basis to keep the index in sync with the database.
 
+(wagtailsearch_backends_atomic_rebuild)=
 
-.. _wagtailsearch_backends_atomic_rebuild:
+## `ATOMIC_REBUILD`
 
-``ATOMIC_REBUILD``
-==================
+```{warning}
+This option may not work on Elasticsearch version 5.4.x, due to [a bug in the handling of aliases](https://github.com/elastic/elasticsearch/issues/24644) - please upgrade to 5.5 or later.
+```
 
-.. warning::
-    This option may not work on Elasticsearch version 5.4.x, due to `a bug in the handling of aliases <https://github.com/elastic/elasticsearch/issues/24644>`_ - please upgrade to 5.5 or later.
+By default (when using the Elasticsearch backend), when the `update_index` command is run, Wagtail deletes the index and rebuilds it from scratch. This causes the search engine to not return results until the rebuild is complete and is also risky as you can't rollback if an error occurs.
 
-By default (when using the Elasticsearch backend), when the ``update_index`` command is run, Wagtail deletes the index and rebuilds it from scratch. This causes the search engine to not return results until the rebuild is complete and is also risky as you can't rollback if an error occurs.
+Setting the `ATOMIC_REBUILD` setting to `True` makes Wagtail rebuild into a separate index while keep the old index active until the new one is fully built. When the rebuild is finished, the indexes are swapped atomically and the old index is deleted.
 
-Setting the ``ATOMIC_REBUILD`` setting to ``True`` makes Wagtail rebuild into a separate index while keep the old index active until the new one is fully built. When the rebuild is finished, the indexes are swapped atomically and the old index is deleted.
-
-``BACKEND``
-===========
+## `BACKEND`
 
 Here's a list of backends that Wagtail supports out of the box.
 
-.. _wagtailsearch_backends_database:
+(wagtailsearch_backends_database)=
 
-Database Backend (default)
---------------------------
+### Database Backend (default)
 
-``wagtail.search.backends.database``
+`wagtail.search.backends.database`
 
 The database search backend searches content in the database using the full text search features of the database backend in use (such as PostgreSQL FTS, SQLite FTS5).
 This backend is intended to be used for development and also should be good enough to use in production on sites that don't require any Elasticsearch specific features.
 
-.. versionchanged:: 2.15
-
-    ``wagtail.search.backends.database`` replaces the old ``wagtail.search.backends.db`` backend which works using simple substring matching only. ``wagtail.search.backends.db`` is still the default if ``WAGTAILSEARCH_BACKENDS`` is not specified; ``wagtail.search.backends.database`` will become the default in Wagtail 2.17.
+```{versionchanged} 2.15
+`wagtail.search.backends.database` replaces the old `wagtail.search.backends.db` backend which works using simple substring matching only. `wagtail.search.backends.db` is still the default if `WAGTAILSEARCH_BACKENDS` is not specified; `wagtail.search.backends.database` will become the default in Wagtail 2.17.
+```
 
+(wagtailsearch_backends_elasticsearch)=
 
-.. _wagtailsearch_backends_elasticsearch:
-
-Elasticsearch Backend
----------------------
+### Elasticsearch Backend
 
 Elasticsearch versions 5, 6 and 7 are supported. Use the appropriate backend for your version:
 
-``wagtail.search.backends.elasticsearch5`` (Elasticsearch 5.x)
-
-``wagtail.search.backends.elasticsearch6`` (Elasticsearch 6.x)
-
-``wagtail.search.backends.elasticsearch7`` (Elasticsearch 7.x)
-
-Prerequisites are the `Elasticsearch`_ service itself and, via pip, the `elasticsearch-py`_ package. The major version of the package must match the installed version of Elasticsearch:
-
-.. _Elasticsearch: https://www.elastic.co/downloads/elasticsearch
-
-.. code-block:: sh
-
-  pip install "elasticsearch>=5.0.0,<6.0.0"  # for Elasticsearch 5.x
-
-.. code-block:: sh
-
-  pip install "elasticsearch>=6.4.0,<7.0.0"  # for Elasticsearch 6.x
+-   `wagtail.search.backends.elasticsearch5` (Elasticsearch 5.x)
+-   `wagtail.search.backends.elasticsearch6` (Elasticsearch 6.x)
+-   `wagtail.search.backends.elasticsearch7` (Elasticsearch 7.x)
 
-.. code-block:: sh
+Prerequisites are the [Elasticsearch](https://www.elastic.co/downloads/elasticsearch) service itself and, via pip, the [elasticsearch-py](https://elasticsearch-py.readthedocs.org) package. The major version of the package must match the installed version of Elasticsearch:
 
-  pip install "elasticsearch>=7.0.0,<8.0.0"  # for Elasticsearch 7.x
+```sh
+pip install "elasticsearch>=5.0.0,<6.0.0"  # for Elasticsearch 5.x
+```
 
-.. warning::
+```sh
+pip install "elasticsearch>=6.4.0,<7.0.0"  # for Elasticsearch 6.x
+```
 
-    | Version 6.3.1 of the Elasticsearch client library is incompatible with Wagtail. Use 6.4.0 or above.
+```sh
+pip install "elasticsearch>=7.0.0,<8.0.0"  # for Elasticsearch 7.x
+```
 
+```{warning}
+Version 6.3.1 of the Elasticsearch client library is incompatible with Wagtail. Use 6.4.0 or above.
+```
 
 The backend is configured in settings:
 
-.. code-block:: python
-
-  WAGTAILSEARCH_BACKENDS = {
-      'default': {
-          'BACKEND': 'wagtail.search.backends.elasticsearch5',
-          'URLS': ['http://localhost:9200'],
-          'INDEX': 'wagtail',
-          'TIMEOUT': 5,
-          'OPTIONS': {},
-          'INDEX_SETTINGS': {},
-      }
-  }
-
-Other than ``BACKEND``, the keys are optional and default to the values shown. Any defined key in ``OPTIONS`` is passed directly to the Elasticsearch constructor as case-sensitive keyword argument (e.g. ``'max_retries': 1``).
-
-A username and password may be optionally be supplied to the ``URL`` field to provide authentication credentials for the Elasticsearch service:
-
-.. code-block:: python
-
-  WAGTAILSEARCH_BACKENDS = {
-      'default': {
-          ...
-          'URLS': ['http://username:password@localhost:9200'],
-          ...
-      }
-  }
-
-``INDEX_SETTINGS`` is a dictionary used to override the default settings to create the index. The default settings are defined inside the ``ElasticsearchSearchBackend`` class in the module ``wagtail/wagtail/search/backends/elasticsearch7.py``. Any new key is added, any existing key, if not a dictionary, is replaced with the new value. Here's a sample on how to configure the number of shards and setting the Italian LanguageAnalyzer as the default analyzer:
-
-.. code-block:: python
-
-  WAGTAILSEARCH_BACKENDS = {
-      'default': {
-          ...,
-          'INDEX_SETTINGS': {
-              'settings': {
-                  'index': {
-                      'number_of_shards': 1,
-                  },
-                  'analysis': {
-                      'analyzer': {
-                          'default': {
-                              'type': 'italian'
-                          }
-                      }
-                  }
-              }
-          }
-      }
-
-If you prefer not to run an Elasticsearch server in development or production, there are many hosted services available, including `Bonsai`_, who offer a free account suitable for testing and development. To use Bonsai:
-
--  Sign up for an account at `Bonsai`_
--  Use your Bonsai dashboard to create a Cluster.
--  Configure ``URLS`` in the Elasticsearch entry in ``WAGTAILSEARCH_BACKENDS`` using the Cluster URL from your Bonsai dashboard
--  Run ``./manage.py update_index``
-
-.. _elasticsearch-py: https://elasticsearch-py.readthedocs.org
-.. _Bonsai: https://bonsai.io/
-
-Amazon AWS Elasticsearch
-~~~~~~~~~~~~~~~~~~~~~~~~
-
-The Elasticsearch backend is compatible with `Amazon Elasticsearch Service`_, but requires additional configuration to handle IAM based authentication. This can be done with the `requests-aws4auth`_ package along with the following configuration:
-
-.. code-block:: python
-
-  from elasticsearch import RequestsHttpConnection
-  from requests_aws4auth import AWS4Auth
-
-  WAGTAILSEARCH_BACKENDS = {
-      'default': {
-          'BACKEND': 'wagtail.search.backends.elasticsearch5',
-          'INDEX': 'wagtail',
-          'TIMEOUT': 5,
-          'HOSTS': [{
-              'host': 'YOURCLUSTER.REGION.es.amazonaws.com',
-              'port': 443,
-              'use_ssl': True,
-              'verify_certs': True,
-              'http_auth': AWS4Auth('ACCESS_KEY', 'SECRET_KEY', 'REGION', 'es'),
-          }],
-          'OPTIONS': {
-              'connection_class': RequestsHttpConnection,
-          },
-      }
-  }
-
-.. _Amazon Elasticsearch Service: https://aws.amazon.com/elasticsearch-service/
-.. _requests-aws4auth: https://pypi.python.org/pypi/requests-aws4auth
-
-
-Rolling Your Own
-----------------
-
-Wagtail search backends implement the interface defined in ``wagtail/wagtail/wagtailsearch/backends/base.py``. At a minimum, the backend's ``search()`` method must return a collection of objects or ``model.objects.none()``. For a fully-featured search backend, examine the Elasticsearch backend code in ``elasticsearch.py``.
+```python
+WAGTAILSEARCH_BACKENDS = {
+    'default': {
+        'BACKEND': 'wagtail.search.backends.elasticsearch5',
+        'URLS': ['http://localhost:9200'],
+        'INDEX': 'wagtail',
+        'TIMEOUT': 5,
+        'OPTIONS': {},
+        'INDEX_SETTINGS': {},
+    }
+}
+```
+
+Other than `BACKEND`, the keys are optional and default to the values shown. Any defined key in `OPTIONS` is passed directly to the Elasticsearch constructor as case-sensitive keyword argument (e.g. `'max_retries': 1`).
+
+A username and password may be optionally be supplied to the `URL` field to provide authentication credentials for the Elasticsearch service:
+
+```python
+WAGTAILSEARCH_BACKENDS = {
+    'default': {
+        ...
+        'URLS': ['http://username:password@localhost:9200'],
+        ...
+    }
+}
+```
+
+`INDEX_SETTINGS` is a dictionary used to override the default settings to create the index. The default settings are defined inside the `ElasticsearchSearchBackend` class in the module `wagtail/wagtail/search/backends/elasticsearch7.py`. Any new key is added, any existing key, if not a dictionary, is replaced with the new value. Here's a sample on how to configure the number of shards and setting the Italian LanguageAnalyzer as the default analyzer:
+
+```python
+WAGTAILSEARCH_BACKENDS = {
+    'default': {
+        ...,
+        'INDEX_SETTINGS': {
+            'settings': {
+                'index': {
+                    'number_of_shards': 1,
+                },
+                'analysis': {
+                    'analyzer': {
+                        'default': {
+                            'type': 'italian'
+                        }
+                    }
+                }
+            }
+        }
+    }
+```
+
+If you prefer not to run an Elasticsearch server in development or production, there are many hosted services available, including [Bonsai](https://bonsai.io/), who offer a free account suitable for testing and development. To use Bonsai:
+
+-   Sign up for an account at `Bonsai`
+-   Use your Bonsai dashboard to create a Cluster.
+-   Configure `URLS` in the Elasticsearch entry in `WAGTAILSEARCH_BACKENDS` using the Cluster URL from your Bonsai dashboard
+-   Run `./manage.py update_index`
+
+### Amazon AWS Elasticsearch
+
+The Elasticsearch backend is compatible with [Amazon Elasticsearch Service](https://aws.amazon.com/elasticsearch-service/), but requires additional configuration to handle IAM based authentication. This can be done with the [requests-aws4auth](https://pypi.python.org/pypi/requests-aws4auth) package along with the following configuration:
+
+```python
+from elasticsearch import RequestsHttpConnection
+from requests_aws4auth import AWS4Auth
+
+WAGTAILSEARCH_BACKENDS = {
+    'default': {
+        'BACKEND': 'wagtail.search.backends.elasticsearch5',
+        'INDEX': 'wagtail',
+        'TIMEOUT': 5,
+        'HOSTS': [{
+            'host': 'YOURCLUSTER.REGION.es.amazonaws.com',
+            'port': 443,
+            'use_ssl': True,
+            'verify_certs': True,
+            'http_auth': AWS4Auth('ACCESS_KEY', 'SECRET_KEY', 'REGION', 'es'),
+        }],
+        'OPTIONS': {
+            'connection_class': RequestsHttpConnection,
+        },
+    }
+}
+```
+
+## Rolling Your Own
+
+Wagtail search backends implement the interface defined in `wagtail/wagtail/wagtailsearch/backends/base.py`. At a minimum, the backend's `search()` method must return a collection of objects or `model.objects.none()`. For a fully-featured search backend, examine the Elasticsearch backend code in `elasticsearch.py`.

+ 18 - 25
docs/topics/search/index.md

@@ -1,43 +1,36 @@
+(wagtailsearch)=
 
-.. _wagtailsearch:
-
-======
-Search
-======
+# Search
 
 Wagtail provides a comprehensive and extensible search interface. In addition, it provides ways to promote search results through "Editor's Picks". Wagtail also collects simple statistics on queries made through the search interface.
 
-.. toctree::
-    :maxdepth: 2
-
-    indexing
-    searching
-    backends
-
+```{toctree}
+---
+maxdepth: 2
+---
+indexing
+searching
+backends
+```
 
-Indexing
-========
+## Indexing
 
 To make objects searchable, they must first be added to the search index. This involves configuring the models and fields that you would like to index (which is done for you for Pages, Images and Documents), and then actually inserting them into the index.
 
-See :ref:`wagtailsearch_indexing_update` for information on how to keep the objects in your search index in sync with the objects in your database.
+See [](wagtailsearch_indexing_update) for information on how to keep the objects in your search index in sync with the objects in your database.
 
-If you have created some extra fields in a subclass of ``Page`` or ``Image``, you may want to add these new fields to the search index, so a user's search query can match the Page or Image's extra content. See :ref:`wagtailsearch_indexing_fields`.
+If you have created some extra fields in a subclass of `Page` or `Image`, you may want to add these new fields to the search index, so a user's search query can match the Page or Image's extra content. See [](wagtailsearch_indexing_fields).
 
-If you have a custom model which doesn't derive from ``Page`` or ``Image`` that you would like to make searchable, see :ref:`wagtailsearch_indexing_models`.
+If you have a custom model which doesn't derive from `Page` or `Image` that you would like to make searchable, see [](wagtailsearch_indexing_models).
 
-
-Searching
-=========
+## Searching
 
 Wagtail provides an API for performing search queries on your models. You can also perform search queries on Django QuerySets.
 
-See :ref:`wagtailsearch_searching`.
-
+See [](wagtailsearch_searching).
 
-Backends
-========
+## Backends
 
 Wagtail provides two backends for storing the search index and performing search queries: one using the database's full-text search capabilities, and another using Elasticsearch. It's also possible to roll your own search backend.
 
-See :ref:`wagtailsearch_backends`
+See [](wagtailsearch_backends).

+ 255 - 292
docs/topics/search/searching.md

@@ -1,254 +1,225 @@
+(wagtailsearch_searching)=
 
-.. _wagtailsearch_searching:
+# Searching
 
+(wagtailsearch_searching_pages)=
 
-=========
-Searching
-=========
+## Searching QuerySets
 
+Wagtail search is built on Django's [QuerySet API](django: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.
 
-.. _wagtailsearch_searching_pages:
+### Searching Pages
 
-Searching QuerySets
-===================
+Wagtail provides a shortcut for searching pages: the `.search()` `QuerySet` method. You can call this on any `PageQuerySet`. For example:
 
-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.
+```python
+# Search future EventPages
+>>> from wagtail.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:
 
-Searching Pages
----------------
+```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>]
+```
 
-Wagtail provides a shortcut for searching pages: the ``.search()`` ``QuerySet`` method. You can call this on any ``PageQuerySet``. For example:
+```{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()`.
+```
 
-.. code-block:: python
+Before the `autocomplete()` method was introduced, the search method also did partial matching. This behaviour is will be deprecated and you should either switch to the new `autocomplete()` method or pass `partial_match=False` into the search method to opt-in to the new behaviour.
+The partial matching in `search()` will be completely removed in a future release.
 
-    # Search future EventPages
-    >>> from wagtail.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()``.
-
-
-.. note::
-
-    Before the ``autocomplete()`` method was introduced, the search method also did partial matching. This behaviour is will be deprecated and you should
-    either switch to the new ``autocomplete()`` method or pass ``partial_match=False`` into the search method to opt-in to the new behaviour. The
-    partial matching in ``search()`` will be completely removed in a future release.
-
-
-Autocomplete searches
----------------------
+### Autocomplete searches
 
 Wagtail provides a separate method which performs partial matching on specific autocomplete fields. This is useful for suggesting pages to the user in real-time as they type their query.
 
-.. code-block:: python
-
-    >>> EventPage.objects.live().autocomplete("Eve")
-    [<EventPage: Event 1>, <EventPage: Event 2>]
-
-
-.. tip::
-
-    This method should only be used for real-time autocomplete and actual search requests should always use the ``search()`` method.
-
-
-.. _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:
+```python
+>>> EventPage.objects.live().autocomplete("Eve")
+[<EventPage: Event 1>, <EventPage: Event 2>]
+```
 
-.. code-block:: python
+```{note}
+This method should only be used for real-time autocomplete and actual search requests should always use the `search()` method.
+```
 
-    >>> from wagtail.images.models import Image
+(wagtailsearch_images_documents_custom_models)=
 
-    >>> Image.objects.filter(uploaded_by_user=user).search("Hello")
-    [<Image: Hello>, <Image: Hello world!>]
+### Searching Images, Documents and custom models
 
+Wagtail's document and image models provide a `search` method on their QuerySets, just as pages do:
 
-:ref:`Custom models <wagtailsearch_indexing_models>` can be searched by using the ``search`` method on the search backend directly:
+```python
+>>> from wagtail.images.models import Image
 
-.. code-block:: python
+>>> Image.objects.filter(uploaded_by_user=user).search("Hello")
+[<Image: Hello>, <Image: Hello world!>]
+```
 
-    >>> from myapp.models import Book
-    >>> from wagtail.search.backends import get_search_backend
+[Custom models](wagtailsearch_indexing_models) can be searched by using the `search` method on the search backend directly:
 
-    # Search books
-    >>> s = get_search_backend()
-    >>> s.search("Great", Book)
-    [<Book: Great Expectations>, <Book: The Great Gatsby>]
+```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:
+You can also pass a QuerySet into the `search` method which allows you to add filters to your search results:
 
-.. code-block:: python
+```python
+>>> from myapp.models import Book
+>>> from wagtail.search.backends import get_search_backend
 
-    >>> 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>]
+```
 
-    # 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
 
-.. _wagtailsearch_specifying_fields:
+By default, Wagtail will search all fields that have been indexed using `index.SearchField`.
 
-Specifying the fields to search
--------------------------------
+This can be limited to a certain set of fields by using the `fields` keyword argument:
 
-By default, Wagtail will search all fields that have been indexed using ``index.SearchField``.
+```python
+# Search just the title field
+>>> EventPage.objects.search("Event", fields=["title"])
+[<EventPage: Event 1>, <EventPage: Event 2>]
+```
 
-This can be limited to a certain set of fields by using the ``fields`` keyword argument:
+(wagtailsearch_faceted_search)=
 
-.. 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
---------------
+### 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 `.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")
+```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
-    ])
+# 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
--------------------------
+## Changing search behaviour
 
-Search operator
-^^^^^^^^^^^^^^^
+### 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)
+-   "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:
+Here's an example of using the `operator` keyword argument:
 
-.. code-block:: python
+```python
+# The database contains a "Thing" model with the following items:
+# - Hello world
+# - Hello
+# - World
 
-    # 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")
 
-    # 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>]
 
-    # 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")
 
-    # 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>]
+```
 
-    # 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:
 
-For page, image and document models, the ``operator`` keyword argument is also supported on the QuerySet's ``search`` method:
+```python
+>>> Page.objects.search("Hello world", operator="or")
 
-.. code-block:: python
+# All pages containing either "hello" or "world" are returned
+[<Page: Hello World>, <Page: Hello>, <Page: World>]
+```
 
-    >>> 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
 
 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
+```python
+>>> from wagtail.search.query import Phrase
 
-    >>> from wagtail.search.query import Phrase
+>>> Page.objects.search(Phrase("Hello world"))
+[<Page: Hello World>]
 
-    >>> Page.objects.search(Phrase("Hello world"))
-    [<Page: Hello World>]
+>>> Page.objects.search(Phrase("World hello"))
+[<Page: World Hello day>]
+```
 
-    >>> Page.objects.search(Phrase("World hello"))
-    [<Page: World Hello day>]
+If you are looking to implement phrase queries using the double-quote syntax, see [](wagtailsearch_query_string_parsing).
 
-If you are looking to implement phrase queries using the double-quote syntax, see :ref:`wagtailsearch_query_string_parsing`.
+(fuzzy_matching)=
 
-.. _fuzzy_matching:
+### Fuzzy matching
 
-Fuzzy matching
-^^^^^^^^^^^^^^
+```{versionadded} 4.0
 
-.. versionadded:: 4.0
+```
 
-Fuzzy matching will return documents which contain terms similar to the search term, as measured by a `Levenshtein edit distance <https://en.wikipedia.org/wiki/Levenshtein_distance>`.
+Fuzzy matching will return documents which contain terms similar to the search term, as measured by a [Levenshtein edit distance](https://en.wikipedia.org/wiki/Levenshtein_distance).
 
 A maximum of one edit (transposition, insertion, or removal of a character) is permitted for three to five letter terms, two edits for longer terms, and shorter terms must match exactly.
 
 For example:
 
-.. code-block:: python
-
-    >>> from wagtail.search.query import Fuzzy
+```python
+>>> from wagtail.search.query import Fuzzy
 
-    >>> Page.objects.search(Fuzzy("Hallo"))
-    [<Page: Hello World>]
+>>> Page.objects.search(Fuzzy("Hallo"))
+[<Page: Hello World>]
+```
 
 Fuzzy matching is supported by the Elasticsearch search backend only.
 
+(wagtailsearch_complex_queries)=
 
-.. _wagtailsearch_complex_queries:
-
-Complex search 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)``
+`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.
 
@@ -256,116 +227,112 @@ 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"))
+```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")))
+# 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)``
+`Phrase(query_string)`
 
 This class wraps a string containing a phrase. See previous section for how this works.
 
 For example:
 
-.. code-block:: python
+```python
+# This example will match both the phrases "hello world" and "Hello earth"
+>>> Page.objects.search(Phrase("Hello world") | Phrase("Hello earth"))
+```
 
-    # This example will match both the phrases "hello world" and "Hello earth"
-    >>> Page.objects.search(Phrase("Hello world") | Phrase("Hello earth"))
-
-``Boost(query, boost)``
+`Boost(query, boost)`
 
 This class boosts the score of another query.
 
 For example:
 
-.. code-block:: python
-
-    >>> from wagtail.search.query import PlainText, Boost
+```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"))
+# 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:
+(wagtailsearch_query_string_parsing)=
 
-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``).
+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
+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')
+```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',
-    }
+>>> filters
+{
+    'this-is-a': 'filter',
+}
 
-    >>> query
-    And([
-        PlainText("my query string", operator='and'),
-        Phrase("this is a phrase"),
-    ])
+>>> 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
+```python
+from wagtail.search.utils import parse_query_string
 
-    from wagtail.search.utils import parse_query_string
+def search(request):
+    query_string = request.GET['query']
 
-    def search(request):
-        query_string = request.GET['query']
+    # Parse query
+    filters, query = parse_query_string(query_string, operator='and')
 
-        # 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)
 
-        # 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)
 
-        # Search
-        pages = pages.search(query)
+    return render(request, 'search_results.html', {'pages': pages})
+```
 
-        return render(request, 'search_results.html', {'pages': pages})
+### Custom ordering
 
-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.
+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)
+```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>]
+# Events ordered by date
+[<EventPage: Easter>, <EventPage: Halloween>, <EventPage: Christmas>]
+```
 
+(wagtailsearch_annotating_results_with_score)=
 
-.. _wagtailsearch_annotating_results_with_score:
-
-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
@@ -373,96 +340,92 @@ 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``.
+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),
-    ("Halloween", 1.7),
-    ("Christmas", 1.5),
+```python
+>>> events = EventPage.objects.search("Event").annotate_score("_score")
+>>> for event in events:
+...    print(event.title, event._score)
+...
+("Easter", 2.5),
+("Halloween", 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:
+(wagtailsearch_frontend_views)=
 
-An example page search view
-===========================
+## 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
+```python
+# views.py
 
-    from wagtail.models import Page
-    from wagtail.search.models import Query
+from django.shortcuts import render
 
+from wagtail.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()
+def search(request):
+    # Search
+    search_query = request.GET.get('query', None)
+    if search_query:
+        search_results = Page.objects.live().search(search_query)
 
-        # Render template
-        return render(request, 'search_results.html', {
-            'search_query': search_query,
-            'search_results': search_results,
-        })
+        # 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+django
-
-    {% 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
-=======================
+```html+django
+{% 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.
+This functionality is provided by the {mod}`~wagtail.contrib.search_promotions` contrib module.