Browse Source

convert various documentation pages to Markdown

- Removes use of topic:: in docs as this directive is not needed
- resolves #8323
Daniel Kirkham 2 years ago
parent
commit
c877bf9886

+ 1 - 0
CHANGELOG.txt

@@ -5,6 +5,7 @@ unreleased (xx.xx.xxxx) - IN DEVELOPMENT
 ~~~~~~~~~~~~~~~~
 
  * Add clarity to confirmation when being asked to convert an external link to an internal one (Thijs Kramer)
+ * Convert various pages in the documentation to Markdown (Daniel Kirkham)
 
 
 3.0 (xx.xx.xxxx) - IN DEVELOPMENT

+ 1 - 0
CONTRIBUTORS.rst

@@ -591,6 +591,7 @@ Contributors
 * Kyle J. Roux
 * Vaibhav Shukla
 * Rishank Kanaparti
+* Daniel Kirkham
 
 Translators
 ===========

+ 409 - 0
docs/advanced_topics/embeds.md

@@ -0,0 +1,409 @@
+(embedded_content)=
+
+# Embedded content
+
+Wagtail supports generating embed code from URLs to content on external
+providers such as Youtube or Twitter. By default, Wagtail will fetch the embed
+code directly from the relevant provider's site using the oEmbed protocol.
+
+Wagtail has a built-in list of the most common providers and this list can be
+changed [with a setting](customising_embed_providers). Wagtail also supports
+fetching embed code using [Embedly](Embedly) and [custom embed finders](custom_embed_finders).
+
+## Embedding content on your site
+
+Wagtail's embeds module should work straight out of the box for most providers.
+You can use any of the following methods to call the module:
+
+### Rich text
+
+Wagtail's default rich text editor has a "media" icon that allows embeds to be
+placed into rich text. You don't have to do anything to enable this; just make
+sure the rich text field's content is being passed through the ``|richtext``
+filter in the template as this is what calls the embeds module to fetch and
+nest the embed code.
+
+### `EmbedBlock` StreamField block type
+
+The `EmbedBlock` block type allows embeds
+to be placed into a `StreamField`.
+
+The `max_width` and `max_height` arguments are sent to the provider when fetching the embed code.
+
+For example:
+
+```python
+from wagtail.embeds.blocks import EmbedBlock
+
+class MyStreamField(blocks.StreamBlock):
+    ...
+
+    embed = EmbedBlock(max_width=800, max_height=400)
+```
+
+### `{% embed %}` tag
+
+Syntax: `{% embed <url> [max_width=<max width>] %}`
+
+You can nest embeds into a template by passing the URL and an optional
+`max_width` argument to the `{% embed %}` tag.
+
+The `max_width` argument is sent to the provider when fetching the embed code.
+
+```html+Django
+{% load wagtailembeds_tags %}
+
+{# Embed a YouTube video #}
+{% embed 'https://www.youtube.com/watch?v=Ffu-2jEdLPw' %}
+
+{# This tag can also take the URL from a variable #}
+{% embed page.video_url %}
+```
+
+### From Python
+
+You can also call the internal `get_embed` function that takes a URL string
+and returns an `Embed` object (see model documentation below). This also
+takes a `max_width` keyword argument that is sent to the provider when
+fetching the embed code.
+
+```python
+from wagtail.embeds.embeds import get_embed
+from wagtail.embeds.exceptions import EmbedException
+
+try:
+    embed = get_embed('https://www.youtube.com/watch?v=Ffu-2jEdLPw')
+
+    print(embed.html)
+except EmbedException:
+    # Cannot find embed
+    pass
+```
+
+(configuring_embed_finders)=
+
+## Configuring embed "finders"
+
+Embed finders are the modules within Wagtail that are responsible for producing
+embed code from a URL.
+
+Embed finders are configured using the `WAGTAILEMBEDS_FINDERS` setting. This
+is a list of finder configurations that are each run in order until one of them
+successfully returns an embed:
+
+The default configuration is:
+
+```python
+WAGTAILEMBEDS_FINDERS = [
+    {
+        'class': 'wagtail.embeds.finders.oembed'
+    }
+]
+```
+
+(oEmbed)=
+
+### oEmbed (default)
+
+The default embed finder fetches the embed code directly from the content
+provider using the oEmbed protocol. Wagtail has a built-in list of providers
+which are all enabled by default. You can find that provider list at the
+following link:
+
+<https://github.com/wagtail/wagtail/blob/main/wagtail/embeds/oembed_providers.py>
+
+(customising_embed_providers)=
+
+#### Customising the provider list
+
+You can limit which providers may be used by specifying the list of providers
+in the finder configuration.
+
+For example, this configuration will only allow content to be nested from Vimeo
+and Youtube. It also adds a custom provider:
+
+```python
+from wagtail.embeds.oembed_providers import youtube, vimeo
+
+# Add a custom provider
+# Your custom provider must support oEmbed for this to work. You should be
+# able to find these details in the provider's documentation.
+# - 'endpoint' is the URL of the oEmbed endpoint that Wagtail will call
+# - 'urls' specifies which patterns
+my_custom_provider = {
+    'endpoint': 'https://customvideosite.com/oembed',
+    'urls': [
+        '^http(?:s)?://(?:www\\.)?customvideosite\\.com/[^#?/]+/videos/.+$',
+    ]
+}
+
+WAGTAILEMBEDS_FINDERS = [
+    {
+        'class': 'wagtail.embeds.finders.oembed',
+        'providers': [youtube, vimeo, my_custom_provider],
+    }
+]
+```
+
+#### Customising an individual provider
+
+Multiple finders can be chained together. This can be used for customising the
+configuration for one provider without affecting the others.
+
+For example, this is how you can instruct Youtube to return videos in HTTPS
+(which must be done explicitly for YouTube):
+
+```python
+from wagtail.embeds.oembed_providers import youtube
+
+
+WAGTAILEMBEDS_FINDERS = [
+    # Fetches YouTube videos but puts ``?scheme=https`` in the GET parameters
+    # when calling YouTube's oEmbed endpoint
+    {
+        'class': 'wagtail.embeds.finders.oembed',
+        'providers': [youtube],
+        'options': {'scheme': 'https'}
+    },
+
+    # Handles all other oEmbed providers the default way
+    {
+        'class': 'wagtail.embeds.finders.oembed',
+    }
+]
+```
+
+#### How Wagtail uses multiple finders
+
+If multiple providers can handle a URL (for example, a YouTube video was
+requested using the configuration above), the topmost finder is chosen to
+perform the request.
+
+Wagtail will not try to run any other finder, even if the chosen one didn't
+return an embed.
+
+
+(facebook_and_instagram_embeds)=
+
+### Facebook and Instagram
+
+As of October 2020, Facebook deprecated their public oEmbed APIs. If you would
+like to embed Facebook or Instagram posts in your site, you will need to
+use the new authenticated APIs. This requires you to set up a Facebook
+Developer Account and create a Facebook App that includes the _oEmbed Product_.
+Instructions for creating the necessary app are in the requirements sections of the
+[Facebook](https://developers.facebook.com/docs/plugins/oembed)
+and [Instagram](https://developers.facebook.com/docs/instagram/oembed) documentation.
+
+As of June 2021, the _oEmbed Product_ has been replaced with the _oEmbed Read_
+feature. In order to embed Facebook and Instagram posts your app must activate
+the _oEmbed Read_ feature. Furthermore the app must be reviewed and accepted
+by Facebook. You can find the announcement in the
+[API changelog](https://developers.facebook.com/docs/graph-api/changelog/version11.0/#oembed).
+
+Apps that activated the oEmbed Product before June 8, 2021 need to activate
+the oEmbed Read feature and review their app before September 7, 2021.
+
+Once you have your app access tokens (App ID and App Secret), add the Facebook and/or
+Instagram finders to your `WAGTAILEMBEDS_FINDERS` setting and configure them with
+the App ID and App Secret from your app:
+
+```python
+WAGTAILEMBEDS_FINDERS = [
+    {
+        'class': 'wagtail.embeds.finders.facebook',
+        'app_id': 'YOUR FACEBOOK APP_ID HERE',
+        'app_secret': 'YOUR FACEBOOK APP_SECRET HERE',
+    },
+    {
+        'class': 'wagtail.embeds.finders.instagram',
+        'app_id': 'YOUR INSTAGRAM APP_ID HERE',
+        'app_secret': 'YOUR INSTAGRAM APP_SECRET HERE',
+    },
+
+    # Handles all other oEmbed providers the default way
+    {
+        'class': 'wagtail.embeds.finders.oembed',
+    }
+]
+```
+
+By default, Facebook and Instagram embeds include some JavaScript that is necessary to
+fully render the embed. In certain cases, this might not be something you want - for
+example, if you have multiple Facebook embeds, this would result in multiple script tags.
+By passing `'omitscript': True` in the configuration, you can indicate that these script
+tags should be omitted from the embed HTML. Note that you will then have to take care of
+loading this script yourself.
+
+
+(Embedly)=
+
+### Embed.ly
+
+[Embed.ly](https://embed.ly) is a paid-for service that can also provide
+embeds for sites that do not implement the oEmbed protocol.
+
+They also provide some helpful features such as giving embeds a consistent look
+and a common video playback API which is useful if your site allows videos to
+be hosted on different providers and you need to implement custom controls for
+them.
+
+Wagtail has built in support for fetching embeds from Embed.ly. To use it,
+first pip install the `Embedly` [python package](https://pypi.org/project/Embedly/).
+
+Now add an embed finder to your `WAGTAILEMBEDS_FINDERS` setting that uses the
+`wagtail.embeds.finders.oembed` class and pass it your API key:
+
+```python
+WAGTAILEMBEDS_FINDERS = [
+    {
+        'class': 'wagtail.embeds.finders.embedly',
+        'key': 'YOUR EMBED.LY KEY HERE'
+    }
+]
+```
+
+(custom_embed_finders)=
+
+### Custom embed finder classes
+
+For complete control, you can create a custom finder class.
+
+Here's a stub finder class that could be used as a skeleton; please read the
+docstrings for details of what each method does:
+
+```python
+from wagtail.embeds.finders.base import EmbedFinder
+
+
+class ExampleFinder(EmbedFinder):
+    def __init__(self, **options):
+        pass
+
+    def accept(self, url):
+        """
+        Returns True if this finder knows how to fetch an embed for the URL.
+
+        This should not have any side effects (no requests to external servers)
+        """
+        pass
+
+    def find_embed(self, url, max_width=None):
+        """
+        Takes a URL and max width and returns a dictionary of information about the
+        content to be used for embedding it on the site.
+
+        This is the part that may make requests to external APIs.
+        """
+        # TODO: Perform the request
+
+        return {
+            'title': "Title of the content",
+            'author_name': "Author name",
+            'provider_name': "Provider name (eg. YouTube, Vimeo, etc)",
+            'type': "Either 'photo', 'video', 'link' or 'rich'",
+            'thumbnail_url': "URL to thumbnail image",
+            'width': width_in_pixels,
+            'height': height_in_pixels,
+            'html': "<h2>The Embed HTML</h2>",
+        }
+```
+
+Once you've implemented all of those methods, you just need to add it to your
+`WAGTAILEMBEDS_FINDERS` setting:
+
+```python
+WAGTAILEMBEDS_FINDERS = [
+    {
+        'class': 'path.to.your.finder.class.here',
+        # Any other options will be passed as kwargs to the __init__ method
+    }
+]
+```
+
+## The `Embed` model
+
+```{eval-rst}
+.. class:: wagtail.embeds.models.Embed
+
+    Embeds are fetched only once and stored in the database so subsequent requests
+    for an individual embed do not hit the embed finders again.
+
+    .. attribute:: url
+
+        (text)
+
+        The URL of the original content of this embed.
+
+    .. attribute:: max_width
+
+        (integer, nullable)
+
+        The max width that was requested.
+
+    .. attribute:: type
+
+        (text)
+
+        The type of the embed. This can be either 'video', 'photo', 'link' or 'rich'.
+
+    .. attribute:: html
+
+        (text)
+
+        The HTML content of the embed that should be placed on the page
+
+    .. attribute:: title
+
+        (text)
+
+        The title of the content that is being embedded.
+
+    .. attribute:: author_name
+
+        (text)
+
+        The author name of the content that is being embedded.
+
+    .. attribute:: provider_name
+
+        (text)
+
+        The provider name of the content that is being embedded.
+
+        For example: YouTube, Vimeo
+
+    .. attribute:: thumbnail_url
+
+        (text)
+
+        a URL to a thumbnail image of the content that is being embedded.
+
+    .. attribute:: width
+
+        (integer, nullable)
+
+        The width of the embed (images and videos only).
+
+    .. attribute:: height
+
+        (integer, nullable)
+
+        The height of the embed (images and videos only).
+
+    .. attribute:: last_updated
+
+        (datetime)
+
+        The Date/time when this embed was last fetched.
+```
+
+### Deleting embeds
+
+As long as your embeds configuration is not broken, deleting items in the
+`Embed` model should be perfectly safe to do. Wagtail will automatically
+repopulate the records that are being used on the site.
+
+You may want to do this if you've changed from oEmbed to Embedly or vice-versa
+as the embed code they generate may be slightly different and lead to
+inconsistency on your site.

+ 0 - 423
docs/advanced_topics/embeds.rst

@@ -1,423 +0,0 @@
-.. _embedded_content:
-
-================
-Embedded content
-================
-
-Wagtail supports generating embed code from URLs to content on external
-providers such as Youtube or Twitter. By default, Wagtail will fetch the embed
-code directly from the relevant provider's site using the oEmbed protocol.
-
-Wagtail has a built-in list of the most common providers and this list can be
-changed :ref:`with a setting <customising_embed_providers>`. Wagtail also supports
-fetching embed code using `Embedly`_ and :ref:`custom embed finders <custom_embed_finders>`.
-
-Embedding content on your site
-==============================
-
-Wagtail's embeds module should work straight out of the box for most providers.
-You can use any of the following methods to call the module:
-
-Rich text
----------
-
-Wagtail's default rich text editor has a "media" icon that allows embeds to be
-placed into rich text. You don't have to do anything to enable this; just make
-sure the rich text field's content is being passed through the ``|richtext``
-filter in the template as this is what calls the embeds module to fetch and
-nest the embed code.
-
-``EmbedBlock`` StreamField block type
--------------------------------------
-
-The :class:`~wagtail.embeds.block.EmbedBlock` block type allows embeds
-to be placed into a ``StreamField``.
-
-The ``max_width`` and ``max_height`` arguments are sent to the provider when fetching the embed code.
-
-For example:
-
-.. code-block:: python
-
-    from wagtail.embeds.blocks import EmbedBlock
-
-    class MyStreamField(blocks.StreamBlock):
-        ...
-
-        embed = EmbedBlock(max_width=800, max_height=400)
-
-``{% embed %}`` tag
--------------------
-
-Syntax: ``{% embed <url> [max_width=<max width>] %}``
-
-You can nest embeds into a template by passing the URL and an optional
-``max_width`` argument to the ``{% embed %}`` tag.
-
-The ``max_width`` argument is sent to the provider when fetching the embed code.
-
-.. code-block:: html+Django
-
-    {% load wagtailembeds_tags %}
-
-    {# Embed a YouTube video #}
-    {% embed 'https://www.youtube.com/watch?v=Ffu-2jEdLPw' %}
-
-    {# This tag can also take the URL from a variable #}
-    {% embed page.video_url %}
-
-From Python
------------
-
-You can also call the internal ``get_embed`` function that takes a URL string
-and returns an ``Embed`` object (see model documentation below). This also
-takes a ``max_width`` keyword argument that is sent to the provider when
-fetching the embed code.
-
-.. code-block:: python
-
-    from wagtail.embeds.embeds import get_embed
-    from wagtail.embeds.exceptions import EmbedException
-
-    try:
-        embed = get_embed('https://www.youtube.com/watch?v=Ffu-2jEdLPw')
-
-        print(embed.html)
-    except EmbedException:
-        # Cannot find embed
-        pass
-
-.. _configuring_embed_finders:
-
-Configuring embed "finders"
-===========================
-
-Embed finders are the modules within Wagtail that are responsible for producing
-embed code from a URL.
-
-Embed finders are configured using the ``WAGTAILEMBEDS_FINDERS`` setting. This
-is a list of finder configurations that are each run in order until one of them
-successfully returns an embed:
-
-The default configuration is:
-
-.. code-block:: python
-
-    WAGTAILEMBEDS_FINDERS = [
-        {
-            'class': 'wagtail.embeds.finders.oembed'
-        }
-    ]
-
-.. _oEmbed:
-
-oEmbed (default)
-----------------
-
-The default embed finder fetches the embed code directly from the content
-provider using the oEmbed protocol. Wagtail has a built-in list of providers
-which are all enabled by default. You can find that provider list at the
-following link:
-
-https://github.com/wagtail/wagtail/blob/main/wagtail/embeds/oembed_providers.py
-
-.. _customising_embed_providers:
-
-Customising the provider list
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-You can limit which providers may be used by specifying the list of providers
-in the finder configuration.
-
-For example, this configuration will only allow content to be nested from Vimeo
-and Youtube. It also adds a custom provider:
-
-.. code-block:: python
-
-    from wagtail.embeds.oembed_providers import youtube, vimeo
-
-    # Add a custom provider
-    # Your custom provider must support oEmbed for this to work. You should be
-    # able to find these details in the provider's documentation.
-    # - 'endpoint' is the URL of the oEmbed endpoint that Wagtail will call
-    # - 'urls' specifies which patterns
-    my_custom_provider = {
-        'endpoint': 'https://customvideosite.com/oembed',
-        'urls': [
-            '^http(?:s)?://(?:www\\.)?customvideosite\\.com/[^#?/]+/videos/.+$',
-        ]
-    }
-
-    WAGTAILEMBEDS_FINDERS = [
-        {
-            'class': 'wagtail.embeds.finders.oembed',
-            'providers': [youtube, vimeo, my_custom_provider],
-        }
-    ]
-
-Customising an individual provider
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Multiple finders can be chained together. This can be used for customising the
-configuration for one provider without affecting the others.
-
-For example, this is how you can instruct Youtube to return videos in HTTPS
-(which must be done explicitly for YouTube):
-
-.. code-block:: python
-
-    from wagtail.embeds.oembed_providers import youtube
-
-
-    WAGTAILEMBEDS_FINDERS = [
-        # Fetches YouTube videos but puts ``?scheme=https`` in the GET parameters
-        # when calling YouTube's oEmbed endpoint
-        {
-            'class': 'wagtail.embeds.finders.oembed',
-            'providers': [youtube],
-            'options': {'scheme': 'https'}
-        },
-
-        # Handles all other oEmbed providers the default way
-        {
-            'class': 'wagtail.embeds.finders.oembed',
-        }
-    ]
-
-.. topic:: How Wagtail uses multiple finders
-
-    If multiple providers can handle a URL (for example, a YouTube video was
-    requested using the configuration above), the topmost finder is chosen to
-    perform the request.
-
-    Wagtail will not try to run any other finder, even if the chosen one didn't
-    return an embed.
-
-
-.. _facebook_and_instagram_embeds:
-
-Facebook and Instagram
-----------------------
-
-As of October 2020, Facebook deprecated their public oEmbed APIs. If you would
-like to embed Facebook or Instagram posts in your site, you will need to
-use the new authenticated APIs. This requires you to set up a Facebook
-Developer Account and create a Facebook App that includes the `oEmbed Product`.
-Instructions for creating the necessary app are in the requirements sections of the
-`Facebook <https://developers.facebook.com/docs/plugins/oembed>`_
-and `Instagram <https://developers.facebook.com/docs/instagram/oembed>`_ documentation.
-
-As of June 2021, the `oEmbed Product` has been replaced with the `oEmbed Read`
-feature. In order to embed Facebook and Instagram posts your app must activate
-the `oEmbed Read` feature. Furthermore the app must be reviewed and accepted
-by Facebook. You can find the announcement in the `API changelog
-<https://developers.facebook.com/docs/graph-api/changelog/version11.0/#oembed>`_.
-
-Apps that activated the oEmbed Product before June 8, 2021 need to activate
-the oEmbed Read feature and review their app before September 7, 2021.
-
-Once you have your app access tokens (App ID and App Secret), add the Facebook and/or
-Instagram finders to your ``WAGTAILEMBEDS_FINDERS`` setting and configure them with
-the App ID and App Secret from your app:
-
-.. code-block:: python
-
-    WAGTAILEMBEDS_FINDERS = [
-        {
-            'class': 'wagtail.embeds.finders.facebook',
-            'app_id': 'YOUR FACEBOOK APP_ID HERE',
-            'app_secret': 'YOUR FACEBOOK APP_SECRET HERE',
-        },
-        {
-            'class': 'wagtail.embeds.finders.instagram',
-            'app_id': 'YOUR INSTAGRAM APP_ID HERE',
-            'app_secret': 'YOUR INSTAGRAM APP_SECRET HERE',
-        },
-
-        # Handles all other oEmbed providers the default way
-        {
-            'class': 'wagtail.embeds.finders.oembed',
-        }
-    ]
-
-By default, Facebook and Instagram embeds include some JavaScript that is necessary to
-fully render the embed. In certain cases, this might not be something you want - for
-example, if you have multiple Facebook embeds, this would result in multiple script tags.
-By passing ``'omitscript': True`` in the configuration, you can indicate that these script
-tags should be omitted from the embed HTML. Note that you will then have to take care of
-loading this script yourself.
-
-
-.. _Embedly:
-
-Embed.ly
---------
-
-`Embed.ly <https://embed.ly>`_ is a paid-for service that can also provide
-embeds for sites that do not implement the oEmbed protocol.
-
-They also provide some helpful features such as giving embeds a consistent look
-and a common video playback API which is useful if your site allows videos to
-be hosted on different providers and you need to implement custom controls for
-them.
-
-Wagtail has built in support for fetching embeds from Embed.ly. To use it,
-first pip install the ``Embedly`` `python package <https://pypi.org/project/Embedly/>`_.
-
-Now add an embed finder to your ``WAGTAILEMBEDS_FINDERS`` setting that uses the
-``wagtail.embeds.finders.oembed`` class and pass it your API key:
-
-.. code-block:: python
-
-    WAGTAILEMBEDS_FINDERS = [
-        {
-            'class': 'wagtail.embeds.finders.embedly',
-            'key': 'YOUR EMBED.LY KEY HERE'
-        }
-    ]
-
-.. _custom_embed_finders:
-
-Custom embed finder classes
----------------------------
-
-For complete control, you can create a custom finder class.
-
-Here's a stub finder class that could be used as a skeleton; please read the
-docstrings for details of what each method does:
-
-.. code-block:: python
-
-    from wagtail.embeds.finders.base import EmbedFinder
-
-
-    class ExampleFinder(EmbedFinder):
-        def __init__(self, **options):
-            pass
-
-        def accept(self, url):
-            """
-            Returns True if this finder knows how to fetch an embed for the URL.
-
-            This should not have any side effects (no requests to external servers)
-            """
-            pass
-
-        def find_embed(self, url, max_width=None):
-            """
-            Takes a URL and max width and returns a dictionary of information about the
-            content to be used for embedding it on the site.
-
-            This is the part that may make requests to external APIs.
-            """
-            # TODO: Perform the request
-
-            return {
-                'title': "Title of the content",
-                'author_name': "Author name",
-                'provider_name': "Provider name (eg. YouTube, Vimeo, etc)",
-                'type': "Either 'photo', 'video', 'link' or 'rich'",
-                'thumbnail_url': "URL to thumbnail image",
-                'width': width_in_pixels,
-                'height': height_in_pixels,
-                'html': "<h2>The Embed HTML</h2>",
-            }
-
-Once you've implemented all of those methods, you just need to add it to your
-``WAGTAILEMBEDS_FINDERS`` setting:
-
-.. code-block:: python
-
-    WAGTAILEMBEDS_FINDERS = [
-        {
-            'class': 'path.to.your.finder.class.here',
-            # Any other options will be passed as kwargs to the __init__ method
-        }
-    ]
-
-The ``Embed`` model
-===================
-
-.. class:: wagtail.embeds.models.Embed
-
-    Embeds are fetched only once and stored in the database so subsequent requests
-    for an individual embed do not hit the embed finders again.
-
-    .. attribute:: url
-
-        (text)
-
-        The URL of the original content of this embed.
-
-    .. attribute:: max_width
-
-        (integer, nullable)
-
-        The max width that was requested.
-
-    .. attribute:: type
-
-        (text)
-
-        The type of the embed. This can be either 'video', 'photo', 'link' or 'rich'.
-
-    .. attribute:: html
-
-        (text)
-
-        The HTML content of the embed that should be placed on the page
-
-    .. attribute:: title
-
-        (text)
-
-        The title of the content that is being embedded.
-
-    .. attribute:: author_name
-
-        (text)
-
-        The author name of the content that is being embedded.
-
-    .. attribute:: provider_name
-
-        (text)
-
-        The provider name of the content that is being embedded.
-
-        For example: YouTube, Vimeo
-
-    .. attribute:: thumbnail_url
-
-        (text)
-
-        a URL to a thumbnail image of the content that is being embedded.
-
-    .. attribute:: width
-
-        (integer, nullable)
-
-        The width of the embed (images and videos only).
-
-    .. attribute:: height
-
-        (integer, nullable)
-
-        The height of the embed (images and videos only).
-
-    .. attribute:: last_updated
-
-        (datetime)
-
-        The Date/time when this embed was last fetched.
-
-Deleting embeds
----------------
-
-As long as your embeds configuration is not broken, deleting items in the
-``Embed`` model should be perfectly safe to do. Wagtail will automatically
-repopulate the records that are being used on the site.
-
-You may want to do this if you've changed from oEmbed to Embedly or vice-versa
-as the embed code they generate may be slightly different and lead to
-inconsistency on your site.

+ 69 - 0
docs/advanced_topics/images/custom_image_model.md

@@ -0,0 +1,69 @@
+(custom_image_model)=
+
+# Custom image models
+
+The `Image` model can be customised, allowing additional fields to be added
+to images.
+
+To do this, you need to add two models to your project:
+
+- The image model itself that inherits from `wagtail.images.models.AbstractImage`. This is where you would add your additional fields
+- The renditions model that inherits from `wagtail.images.models.AbstractRendition`. This is used to store renditions for the new model.
+
+Here's an example:
+
+```python
+# models.py
+from django.db import models
+
+from wagtail.images.models import Image, AbstractImage, AbstractRendition
+
+
+class CustomImage(AbstractImage):
+    # Add any extra fields to image here
+
+    # eg. To add a caption field:
+    # caption = models.CharField(max_length=255, blank=True)
+
+    admin_form_fields = Image.admin_form_fields + (
+        # Then add the field names here to make them appear in the form:
+        # 'caption',
+    )
+
+
+class CustomRendition(AbstractRendition):
+    image = models.ForeignKey(CustomImage, on_delete=models.CASCADE, related_name='renditions')
+
+    class Meta:
+        unique_together = (
+            ('image', 'filter_spec', 'focal_point_key'),
+        )
+```
+
+Then set the `WAGTAILIMAGES_IMAGE_MODEL` setting to point to it:
+
+```python
+WAGTAILIMAGES_IMAGE_MODEL = 'images.CustomImage'
+```
+
+## Migrating from the builtin image model
+
+When changing an existing site to use a custom image model, no images will
+be copied to the new model automatically. Copying old images to the new
+model would need to be done manually with a
+{ref}`data migration <django:data-migrations>`.
+
+Any templates that reference the builtin image model will still continue to
+work as before but would need to be updated in order to see any new images.
+
+(custom_image_model_referring_to_image_model)=
+
+## Referring to the image model
+
+```{eval-rst}
+.. module:: wagtail.images
+
+.. autofunction:: get_image_model
+
+.. autofunction:: get_image_model_string
+```

+ 0 - 72
docs/advanced_topics/images/custom_image_model.rst

@@ -1,72 +0,0 @@
-.. _custom_image_model:
-
-===================
-Custom image models
-===================
-
-The ``Image`` model can be customised, allowing additional fields to be added
-to images.
-
-To do this, you need to add two models to your project:
-
-- The image model itself that inherits from ``wagtail.images.models.AbstractImage``. This is where you would add your additional fields
-- The renditions model that inherits from ``wagtail.images.models.AbstractRendition``. This is used to store renditions for the new model.
-
-Here's an example:
-
-.. code-block:: python
-
-    # models.py
-    from django.db import models
-
-    from wagtail.images.models import Image, AbstractImage, AbstractRendition
-
-
-    class CustomImage(AbstractImage):
-        # Add any extra fields to image here
-
-        # eg. To add a caption field:
-        # caption = models.CharField(max_length=255, blank=True)
-
-        admin_form_fields = Image.admin_form_fields + (
-            # Then add the field names here to make them appear in the form:
-            # 'caption',
-        )
-
-
-    class CustomRendition(AbstractRendition):
-        image = models.ForeignKey(CustomImage, on_delete=models.CASCADE, related_name='renditions')
-
-        class Meta:
-            unique_together = (
-                ('image', 'filter_spec', 'focal_point_key'),
-            )
-
-
-Then set the ``WAGTAILIMAGES_IMAGE_MODEL`` setting to point to it:
-
-.. code-block:: python
-
-    WAGTAILIMAGES_IMAGE_MODEL = 'images.CustomImage'
-
-
-.. topic:: Migrating from the builtin image model
-
-    When changing an existing site to use a custom image model, no images will
-    be copied to the new model automatically. Copying old images to the new
-    model would need to be done manually with a
-    :ref:`data migration <django:data-migrations>`.
-
-    Any templates that reference the builtin image model will still continue to
-    work as before but would need to be updated in order to see any new images.
-
-.. _custom_image_model_referring_to_image_model:
-
-Referring to the image model
-============================
-
-.. module:: wagtail.images
-
-.. autofunction:: get_image_model
-
-.. autofunction:: get_image_model_string

+ 127 - 132
docs/reference/pages/panels.rst → docs/reference/pages/panels.md

@@ -1,20 +1,21 @@
-.. _editing-api:
+(editing-api)=
 
-Panel types
-===========
+# Panel types
 
-Built-in Fields and Choosers
-----------------------------
+## Built-in Fields and Choosers
 
-Django's field types are automatically recognised and provided with an appropriate widget for input. Just define that field the normal Django way and pass the field name into :class:`~wagtail.admin.panels.FieldPanel` when defining your panels. Wagtail will take care of the rest.
+Django's field types are automatically recognised and provided with an appropriate widget for input. Just define that field the normal Django way and pass the field name into
+[`FieldPanel`](wagtail.admin.panels.FieldPanel) when defining your panels. Wagtail will take care of the rest.
 
 Here are some Wagtail-specific types that you might include as fields in your models.
 
+```{eval-rst}
 .. module:: wagtail.admin.panels
+```
 
-FieldPanel
-~~~~~~~~~~
+### FieldPanel
 
+```{eval-rst}
 .. class:: FieldPanel(field_name, classname=None, widget=None, heading='', disable_comments=False, permission=None)
 
     This is the panel used for basic Django field types.
@@ -46,11 +47,11 @@ FieldPanel
     .. attribute:: FieldPanel.permission (optional)
 
         Allows a field to be selectively shown to users with sufficient permission. Accepts a permission codename such as ``'myapp.change_blog_category'`` - if the logged-in user does not have that permission, the field will be omitted from the form. See Django's documentation on :ref:`custom permissions <django:custom-permissions>` for details on how to set permissions up; alternatively, if you want to set a field as only available to superusers, you can use any arbitrary string (such as ``'superuser'``) as the codename, since superusers automatically pass all permission tests.
+```
 
+### StreamFieldPanel
 
-StreamFieldPanel
-~~~~~~~~~~~~~~~~
-
+```{eval-rst}
 .. class:: StreamFieldPanel(field_name, classname=None, widget=None)
 
     Deprecated; use ``FieldPanel`` instead.
@@ -58,11 +59,11 @@ StreamFieldPanel
     .. versionchanged:: 3.0
 
        ``StreamFieldPanel`` is no longer required for ``StreamField``.
+```
 
+### MultiFieldPanel
 
-MultiFieldPanel
-~~~~~~~~~~~~~~~
-
+```{eval-rst}
 .. class:: MultiFieldPanel(children, heading="", classname=None)
 
     This panel condenses several :class:`~wagtail.admin.panels.FieldPanel` s or choosers, from a ``list`` or ``tuple``, under a single ``heading`` string.
@@ -74,27 +75,27 @@ MultiFieldPanel
     .. attribute:: MultiFieldPanel.heading
 
         A heading for the fields
+```
 
+### InlinePanel
 
-InlinePanel
-~~~~~~~~~~~
-
+```{eval-rst}
 .. class:: InlinePanel(relation_name, panels=None, classname='', heading='', label='', help_text='', min_num=None, max_num=None)
 
     This panel allows for the creation of a "cluster" of related objects over a join to a separate model, such as a list of related links or slides to an image carousel.
+```
 
-    This is a powerful but complex feature which will take some space to cover, so we'll skip over it for now. For a full explanation on the usage of ``InlinePanel``, see :ref:`inline_panels`.
+This is a powerful but complex feature which will take some space to cover, so we'll skip over it for now. For a full explanation on the usage of `InlinePanel`, see {ref}`inline_panels`.
 
-.. topic:: Collapsing InlinePanels to save space
+#### Collapsing InlinePanels to save space
 
-    Note that you can use ``classname="collapsible collapsed"`` to load the panel collapsed under its heading in order to save space in the Wagtail admin.
-    See :ref:`collapsible` for more details on ``collapsible`` usage.
+Note that you can use `classname="collapsible collapsed"` to load the panel collapsed under its heading in order to save space in the Wagtail admin.
+See {ref}`collapsible` for more details on `collapsible` usage.
 
 
+### FieldRowPanel
 
-FieldRowPanel
-~~~~~~~~~~~~~
-
+```{eval-rst}
 .. class:: FieldRowPanel(children, classname=None)
 
     This panel creates a columnar layout in the editing interface, where each of the child Panels appears alongside each other rather than below.
@@ -110,10 +111,11 @@ FieldRowPanel
     .. attribute:: FieldRowPanel.classname
 
         A class to apply to the FieldRowPanel as a whole
+```
 
-HelpPanel
-~~~~~~~~~
+### HelpPanel
 
+```{eval-rst}
 .. class:: HelpPanel(content='', template='wagtailadmin/panels/help_panel.html', heading='', classname='')
 
     .. attribute:: HelpPanel.content
@@ -131,10 +133,11 @@ HelpPanel
     .. attribute:: HelpPanel.classname
 
         String of CSS classes given to the panel which are used in formatting and scripted interactivity.
+```
 
-PageChooserPanel
-~~~~~~~~~~~~~~~~
+### PageChooserPanel
 
+```{eval-rst}
 .. class:: PageChooserPanel(field_name, page_type=None, can_choose_root=False)
 
     You can explicitly link :class:`~wagtail.models.Page`-derived models together using the :class:`~wagtail.models.Page` model and ``PageChooserPanel``.
@@ -169,11 +172,11 @@ PageChooserPanel
     .. versionchanged:: 3.0
 
        ``FieldPanel`` now also provides a page chooser interface for foreign keys to page models. ``PageChooserPanel`` is only required when specifying the ``page_type`` or ``can_choose_root`` parameters.
+```
 
+### ImageChooserPanel
 
-ImageChooserPanel
-~~~~~~~~~~~~~~~~~
-
+```{eval-rst}
 .. module:: wagtail.images.edit_handlers
 
 .. class:: ImageChooserPanel(field_name)
@@ -183,11 +186,11 @@ ImageChooserPanel
     .. versionchanged:: 3.0
 
        ``ImageChooserPanel`` is no longer required to obtain an image chooser interface.
+```
 
+### FormSubmissionsPanel
 
-FormSubmissionsPanel
-~~~~~~~~~~~~~~~~~~~~
-
+```{eval-rst}
 .. module:: wagtail.contrib.forms.panels
 
 .. class:: FormSubmissionsPanel
@@ -204,10 +207,11 @@ FormSubmissionsPanel
             content_panels = [
                 FormSubmissionsPanel(),
             ]
+```
 
-DocumentChooserPanel
-~~~~~~~~~~~~~~~~~~~~
+### DocumentChooserPanel
 
+```{eval-rst}
 .. module:: wagtail.documents.edit_handlers
 
 .. class:: DocumentChooserPanel(field_name)
@@ -217,11 +221,11 @@ DocumentChooserPanel
     .. versionchanged:: 3.0
 
        ``DocumentChooserPanel`` is no longer required to obtain a document chooser interface.
+```
 
+### SnippetChooserPanel
 
-SnippetChooserPanel
-~~~~~~~~~~~~~~~~~~~
-
+```{eval-rst}
 .. module:: wagtail.snippets.edit_handlers
 
 .. class:: SnippetChooserPanel(field_name, snippet_type=None)
@@ -231,41 +235,37 @@ SnippetChooserPanel
     .. versionchanged:: 3.0
 
        ``SnippetChooserPanel`` is no longer required to obtain a document chooser interface.
+```
 
+## Field Customisation
 
-Field Customisation
--------------------
-
-By adding CSS classes to your panel definitions or adding extra parameters to your field definitions, you can control much of how your fields will display in the Wagtail page editing interface. Wagtail's page editing interface takes much of its behaviour from Django's admin, so you may find many options for customisation covered there. (See :doc:`Django model field reference <ref/models/fields>`).
+By adding CSS classes to your panel definitions or adding extra parameters to your field definitions, you can control much of how your fields will display in the Wagtail page editing interface. Wagtail's page editing interface takes much of its behaviour from Django's admin, so you may find many options for customisation covered there.
+(See {doc}`Django model field reference <django:ref/models/fields>`).
 
+### Full-Width Input
 
-Full-Width Input
-~~~~~~~~~~~~~~~~
+Use `classname="full"` to make a field (input element) stretch the full width of the Wagtail page editor. This will not work if the field is encapsulated in a
+[`MultiFieldPanel`](wagtail.admin.panels.MultiFieldPanel), which places its child fields into a formset.
 
-Use ``classname="full"`` to make a field (input element) stretch the full width of the Wagtail page editor. This will not work if the field is encapsulated in a :class:`~wagtail.admin.panels.MultiFieldPanel`, which places its child fields into a formset.
 
-
-Titles
-~~~~~~
+### Titles
 
 Use ``classname="title"`` to make Page's built-in title field stand out with more vertical padding.
 
 
-.. _collapsible:
+(collapsible)=
 
-Collapsible
-~~~~~~~~~~~
+### Collapsible
 
 By default, panels are expanded and not collapsible.
-Use ``classname="collapsible"`` to enable the collapse control.
-Use ``classname="collapsible collapsed"`` will load the editor page with the panel collapsed under its heading.
-
-You must define a ``heading`` when using ``collapsible`` with ``MultiFieldPanel``.
-You must define a ``heading`` or ``label`` when using ``collapsible`` with ``InlinePanel``.
+Use `classname="collapsible"` to enable the collapse control.
+Use `classname="collapsible collapsed"` will load the editor page with the panel collapsed under its heading.
 
+You must define a `heading` when using `collapsible` with `MultiFieldPanel`.
+You must define a `heading` or `label` when using `collapsible` with `InlinePanel`.
 
-.. code-block:: python
 
+```python
     content_panels = [
         MultiFieldPanel(
             [
@@ -277,105 +277,100 @@ You must define a ``heading`` or ``label`` when using ``collapsible`` with ``Inl
             classname="collapsible collapsed"
         ),
     ]
+```
 
+### Placeholder Text
 
-Placeholder Text
-~~~~~~~~~~~~~~~~
-
-By default, Wagtail uses the field's label as placeholder text. To change it, pass to the FieldPanel a widget with a placeholder attribute set to your desired text. You can select widgets from :doc:`Django's form widgets <django:ref/forms/widgets>`, or any of the Wagtail's widgets found in ``wagtail.admin.widgets``.
+By default, Wagtail uses the field's label as placeholder text. To change it, pass to the FieldPanel a widget with a placeholder attribute set to your desired text. You can select widgets from {doc}`Django's form widgets <django:ref/forms/widgets>`, or any of the Wagtail's widgets found in `wagtail.admin.widgets`.
 
 For example, to customize placeholders for a Book model exposed via ModelAdmin:
 
-.. code-block:: python
-
-    # models.py
-    from django import forms            # the default Django widgets live here
-    from wagtail.admin import widgets   # to use Wagtail's special datetime widget
-
-    class Book(models.Model):
-        title = models.CharField(max_length=256)
-        release_date = models.DateField()
-        price = models.DecimalField(max_digits=5, decimal_places=2)
-
-        # you can create them separately
-        title_widget = forms.TextInput(
-            attrs = {
-                'placeholder': 'Enter Full Title'
-            }
-        )
-        # using the correct widget for your field type and desired effect
-        date_widget = widgets.AdminDateInput(
-            attrs = {
-                'placeholder': 'dd-mm-yyyy'
-            }
-        )
-
-        panels = [
-            FieldPanel('title', widget=title_widget), # then add them as a variable
-            FieldPanel('release_date', widget=date_widget),
-            FieldPanel('price', widget=forms.NumberInput(attrs={'placeholder': 'Retail price on release'})) # or directly inline
-        ]
-
-Required Fields
-~~~~~~~~~~~~~~~
+```python
+# models.py
+from django import forms            # the default Django widgets live here
+from wagtail.admin import widgets   # to use Wagtail's special datetime widget
+
+class Book(models.Model):
+    title = models.CharField(max_length=256)
+    release_date = models.DateField()
+    price = models.DecimalField(max_digits=5, decimal_places=2)
+
+    # you can create them separately
+    title_widget = forms.TextInput(
+        attrs = {
+            'placeholder': 'Enter Full Title'
+        }
+    )
+    # using the correct widget for your field type and desired effect
+    date_widget = widgets.AdminDateInput(
+        attrs = {
+            'placeholder': 'dd-mm-yyyy'
+        }
+    )
+
+    panels = [
+        FieldPanel('title', widget=title_widget), # then add them as a variable
+        FieldPanel('release_date', widget=date_widget),
+        FieldPanel('price', widget=forms.NumberInput(attrs={'placeholder': 'Retail price on release'})) # or directly inline
+    ]
+```
 
-To make input or chooser selection mandatory for a field, add :attr:`blank=False <django.db.models.Field.blank>` to its model definition.
+### Required Fields
 
-Hiding Fields
-~~~~~~~~~~~~~
+To make input or chooser selection mandatory for a field, add [`blank=False`](<django.db.models.Field.blank>) to its model definition.
 
-Without a panel definition, a default form field (without label) will be used to represent your fields. If you intend to hide a field on the Wagtail page editor, define the field with :attr:`editable=False <django.db.models.Field.editable>`.
+### Hiding Fields
 
-.. _inline_panels:
+Without a panel definition, a default form field (without label) will be used to represent your fields. If you intend to hide a field on the Wagtail page editor, define the field with [`editable=False`](<django.db.models.Field.editable>).
 
-Inline Panels and Model Clusters
---------------------------------
+(inline_panels)=
 
-The ``django-modelcluster`` module allows for streamlined relation of extra models to a Wagtail page via a ForeignKey-like relationship called ``ParentalKey``.  Normally, your related objects "cluster" would need to be created beforehand (or asynchronously) before being linked to a Page; however, objects related to a Wagtail page via ``ParentalKey`` can be created on-the-fly and saved to a draft revision of a ``Page`` object.
+## Inline Panels and Model Clusters
 
-Let's look at the example of adding related links to a :class:`~wagtail.models.Page`-derived model. We want to be able to add as many as we like, assign an order, and do all of this without leaving the page editing screen.
+The `django-modelcluster` module allows for streamlined relation of extra models to a Wagtail page via a ForeignKey-like relationship called `ParentalKey`.  Normally, your related objects "cluster" would need to be created beforehand (or asynchronously) before being linked to a Page; however, objects related to a Wagtail page via `ParentalKey` can be created on-the-fly and saved to a draft revision of a `Page` object.
 
-.. code-block:: python
+Let's look at the example of adding related links to a [`Page`](wagtail.models.Page)-derived model. We want to be able to add as many as we like, assign an order, and do all of this without leaving the page editing screen.
 
-  from wagtail.models import Orderable, Page
-  from modelcluster.fields import ParentalKey
+```python
+from wagtail.models import Orderable, Page
+from modelcluster.fields import ParentalKey
 
-  # The abstract model for related links, complete with panels
-  class RelatedLink(models.Model):
-      title = models.CharField(max_length=255)
-      link_external = models.URLField("External link", blank=True)
+# The abstract model for related links, complete with panels
+class RelatedLink(models.Model):
+    title = models.CharField(max_length=255)
+    link_external = models.URLField("External link", blank=True)
 
-      panels = [
-          FieldPanel('title'),
-          FieldPanel('link_external'),
-      ]
+    panels = [
+        FieldPanel('title'),
+        FieldPanel('link_external'),
+    ]
 
-      class Meta:
-          abstract = True
+    class Meta:
+        abstract = True
 
-  # The real model which combines the abstract model, an
-  # Orderable helper class, and what amounts to a ForeignKey link
-  # to the model we want to add related links to (BookPage)
-  class BookPageRelatedLinks(Orderable, RelatedLink):
-      page = ParentalKey('demo.BookPage', on_delete=models.CASCADE, related_name='related_links')
+# The real model which combines the abstract model, an
+# Orderable helper class, and what amounts to a ForeignKey link
+# to the model we want to add related links to (BookPage)
+class BookPageRelatedLinks(Orderable, RelatedLink):
+    page = ParentalKey('demo.BookPage', on_delete=models.CASCADE, related_name='related_links')
 
-  class BookPage(Page):
+class BookPage(Page):
     # ...
 
     content_panels = Page.content_panels + [
-      InlinePanel('related_links', label="Related Links"),
+        InlinePanel('related_links', label="Related Links"),
     ]
+```
 
-The ``RelatedLink`` class is a vanilla Django abstract model. The ``BookPageRelatedLinks`` model extends it with capability for being ordered in the Wagtail interface via the ``Orderable`` class as well as adding a ``page`` property which links the model to the ``BookPage`` model we're adding the related links objects to. Finally, in the panel definitions for ``BookPage``, we'll add an :class:`~wagtail.admin.panels.InlinePanel` to provide an interface for it all. Let's look again at the parameters that :class:`~wagtail.admin.panels.InlinePanel` accepts:
-
-.. code-block:: python
-
-    InlinePanel( relation_name, panels=None, heading='', label='', help_text='', min_num=None, max_num=None )
+The `RelatedLink` class is a vanilla Django abstract model. The `BookPageRelatedLinks` model extends it with capability for being ordered in the Wagtail interface via the `Orderable` class as well as adding a `page` property which links the model to the `BookPage` model we're adding the related links objects to. Finally, in the panel definitions for `BookPage`, we'll add an [`InlinePanel`](wagtail.admin.panels.InlinePanel) to provide an interface for it all. Let's look again at the parameters that [`InlinePanel`](wagtail.admin.panels.InlinePanel) accepts:
 
-The ``relation_name`` is the ``related_name`` label given to the cluster's ``ParentalKey`` relation. You can add the ``panels`` manually or make them part of the cluster model. ``heading`` and ``help_text`` provide a heading and caption, respectively, for the Wagtail editor. ``label`` sets the text on the add button, and is used as the heading when ``heading`` is not present. Finally, ``min_num`` and ``max_num`` allow you to set the minimum/maximum number of forms that the user must submit.
+```python
+InlinePanel(relation_name, panels=None, heading='', label='', help_text='', min_num=None, max_num=None)
+```
 
-For another example of using model clusters, see :ref:`tagging`
+The `relation_name` is the `related_name` label given to the cluster's `ParentalKey` relation. You can add the `panels` manually or make them part of the cluster model. `heading` and `help_text` provide a heading and caption, respectively, for the Wagtail editor. `label` sets the text on the add button, and is used as the heading when `heading` is not present. Finally, `min_num` and `max_num` allow you to set the minimum/maximum number of forms that the user must submit.
 
-For more on ``django-modelcluster``, visit `the django-modelcluster github project page`_.
+For another example of using model clusters, see {ref}`tagging`.
 
-.. _the django-modelcluster github project page: https://github.com/torchbox/django-modelcluster
+For more on `django-modelcluster`, visit
+[the django-modelcluster github project page](https://github.com/torchbox/django-modelcluster)

+ 248 - 0
docs/topics/search/indexing.md

@@ -0,0 +1,248 @@
+(wagtailsearch_indexing)=
+
+# Indexing
+
+To make a model searchable, you'll need to add it into the search index. All pages, images and documents are indexed for you, so you can start searching them right away.
+
+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 too so that a user's search query will match on their content. See {ref}`wagtailsearch_indexing_fields` for info on how to do this.
+
+If you have a custom model that you would like to make searchable, see {ref}`wagtailsearch_indexing_models`.
+
+
+(wagtailsearch_indexing_update)=
+
+## Updating the index
+
+If the search index is kept separate from the database (when using Elasticsearch for example), you need to keep them both in sync. There are two ways to do this: using the search signal handlers, or calling the ``update_index`` command periodically. For best speed and reliability, it's best to use both if possible.
+
+### Signal handlers
+
+``wagtailsearch`` provides some signal handlers which bind to the save/delete signals of all indexed models. This would automatically add and delete them from all backends you have registered in ``WAGTAILSEARCH_BACKENDS``. These signal handlers are automatically registered when the ``wagtail.search`` app is loaded.
+
+In some cases, you may not want your content to be automatically reindexed and instead rely on the ``update_index`` command for indexing. If you need to disable these signal handlers, use one of the following methods:
+
+#### Disabling auto update signal handlers for a model
+
+You can disable the signal handlers for an individual model by adding `search_auto_update = False` as an attribute on the model class.
+
+#### Disabling auto update signal handlers for a search backend/whole site
+
+You can disable the signal handlers for a whole search backend by setting the `AUTO_UPDATE` setting on the backend to `False`.
+
+If all search backends have `AUTO_UPDATE` set to `False`, the signal handlers will be completely disabled for the whole site.
+
+For documentation on the `AUTO_UPDATE` setting, see {ref}`wagtailsearch_backends_auto_update`.
+
+
+### The `update_index` command
+
+Wagtail also provides a command for rebuilding the index from scratch.
+
+`./manage.py update_index`
+
+It is recommended to run this command once a week and at the following times:
+
+- whenever any pages have been created through a script (after an import, for example)
+- whenever any changes have been made to models or search configuration
+
+The search may not return any results while this command is running, so avoid running it at peak times.
+
+```{note}
+The `update_index` command is also aliased as `wagtail_update_index`, for use when another installed package (such as [Haystack](https://haystacksearch.org/)) provides a conflicting `update_index` command. In this case, the other package's entry in `INSTALLED_APPS` should appear above `wagtail.search` so that its `update_index` command takes precedence over Wagtail's.
+```
+
+(wagtailsearch_indexing_fields)=
+
+## Indexing extra fields
+
+Fields must be explicitly added to the `search_fields` property of your `Page`-derived model, in order for you to be able to search/filter on them. This is done by overriding `search_fields` to append a list of extra `SearchField`/`FilterField` objects to it.
+
+
+### Example
+
+This creates an `EventPage` model with two fields: `description` and `date`. `description` is indexed as a `SearchField` and `date` is indexed as a `FilterField`.
+
+
+```python
+from wagtail.search import index
+from django.utils import timezone
+
+class EventPage(Page):
+    description = models.TextField()
+    date = models.DateField()
+
+    search_fields = Page.search_fields + [ # Inherit search_fields from Page
+        index.SearchField('description'),
+        index.FilterField('date'),
+    ]
+
+
+# Get future events which contain the string "Christmas" in the title or description
+>>> EventPage.objects.filter(date__gt=timezone.now()).search("Christmas")
+```
+
+(wagtailsearch_index_searchfield)=
+
+### `index.SearchField`
+
+These are used for performing full-text searches on your models, usually for text fields.
+
+#### Options
+
+- **partial_match** (`boolean`) - Setting this to true allows results to be matched on parts of words. For example, this is set on the title field by default, so a page titled `Hello World!` will be found if the user only types `Hel` into the search box.
+- **boost** (`int/float`) - This allows you to set fields as being more important than others. Setting this to a high number on a field will cause pages with matches in that field to be ranked higher. By default, this is set to 2 on the Page title field and 1 on all other fields.
+- **es_extra** (`dict`) - This field is to allow the developer to set or override any setting on the field in the Elasticsearch mapping. Use this if you want to make use of any Elasticsearch features that are not yet supported in Wagtail.
+
+
+(wagtailsearch_index_filterfield)=
+
+### `index.AutocompleteField`
+
+These are used for autocomplete queries which match partial words. For example, a page titled `Hello World!` will be found if the user only types `Hel` into the search box.
+
+This takes the exact same options as `index.SearchField` (with the exception of `partial_match`, which has no effect).
+
+
+```{note}
+Only index fields that are displayed in the search results with ``index.AutocompleteField``. This allows users to see any words that were partial-matched on.
+```
+
+### `index.FilterField`
+
+These are added to the search index but are not used for full-text searches. Instead, they allow you to run filters on your search results.
+
+
+(wagtailsearch_index_relatedfields)=
+
+### `index.RelatedFields`
+
+This allows you to index fields from related objects. It works on all types of related fields, including their reverse accessors.
+
+For example, if we have a book that has a `ForeignKey` to its author, we can nest the author's `name` and `date_of_birth` fields inside the book:
+
+```python
+from wagtail.search import index
+
+class Book(models.Model, index.Indexed):
+    ...
+
+    search_fields = [
+        index.SearchField('title'),
+        index.FilterField('published_date'),
+
+        index.RelatedFields('author', [
+            index.SearchField('name'),
+            index.FilterField('date_of_birth'),
+        ]),
+    ]
+```
+
+This will allow you to search for books by their author's name.
+
+It works the other way around as well. You can index an author's books, allowing an author to be searched for by the titles of books they've published:
+
+```python
+from wagtail.search import index
+
+class Author(models.Model, index.Indexed):
+    ...
+
+    search_fields = [
+        index.SearchField('name'),
+        index.FilterField('date_of_birth'),
+
+        index.RelatedFields('books', [
+            index.SearchField('title'),
+            index.FilterField('published_date'),
+        ]),
+    ]
+```
+
+#### Filtering on `index.RelatedFields`
+
+It's not possible to filter on any `index.FilterFields` within `index.RelatedFields` using the `QuerySet` API. However, the fields are indexed, so it should be possible to use them by querying Elasticsearch manually.
+
+Filtering on `index.RelatedFields` with the `QuerySet` API is planned for a future release of Wagtail.
+
+(wagtailsearch_indexing_callable_fields)=
+
+### Indexing callables and other attributes
+
+```{note}
+This is not supported in the {ref}`wagtailsearch_backends_database`
+```
+
+Search/filter fields do not need to be Django model fields. They can also be any method or attribute on your model class.
+
+One use for this is indexing the `get_*_display` methods Django creates automatically for fields with choices.
+
+```python
+from wagtail.search import index
+
+class EventPage(Page):
+    IS_PRIVATE_CHOICES = (
+        (False, "Public"),
+        (True, "Private"),
+    )
+
+    is_private = models.BooleanField(choices=IS_PRIVATE_CHOICES)
+
+    search_fields = Page.search_fields + [
+        # Index the human-readable string for searching.
+        index.SearchField('get_is_private_display'),
+
+        # Index the boolean value for filtering.
+        index.FilterField('is_private'),
+    ]
+```
+
+Callables also provide a way to index fields from related models. In the example from {ref}`inline_panels`, to index each BookPage by the titles of its related_links:
+
+```python
+class BookPage(Page):
+    # ...
+    def get_related_link_titles(self):
+        # Get list of titles and concatenate them
+        return '\n'.join(self.related_links.all().values_list('name', flat=True))
+
+    search_fields = Page.search_fields + [
+        # ...
+        index.SearchField('get_related_link_titles'),
+    ]
+```
+
+(wagtailsearch_indexing_models)=
+
+## Indexing custom models
+
+Any Django model can be indexed and searched.
+
+To do this, inherit from `index.Indexed` and add some `search_fields` to the model.
+
+```python
+from wagtail.search import index
+
+class Book(index.Indexed, models.Model):
+    title = models.CharField(max_length=255)
+    genre = models.CharField(max_length=255, choices=GENRE_CHOICES)
+    author = models.ForeignKey(Author, on_delete=models.CASCADE)
+    published_date = models.DateTimeField()
+
+    search_fields = [
+        index.SearchField('title', partial_match=True, boost=10),
+        index.SearchField('get_genre_display'),
+
+        index.FilterField('genre'),
+        index.FilterField('author'),
+        index.FilterField('published_date'),
+    ]
+
+# As this model doesn't have a search method in its QuerySet, we have to call search directly on the backend
+>>> from wagtail.search.backends import get_search_backend
+>>> s = get_search_backend()
+
+# Run a search for a book by Roald Dahl
+>>> roald_dahl = Author.objects.get(name="Roald Dahl")
+>>> s.search("chocolate factory", Book.objects.filter(author=roald_dahl))
+[<Book: Charlie and the chocolate factory>]
+```

+ 0 - 272
docs/topics/search/indexing.rst

@@ -1,272 +0,0 @@
-
-.. _wagtailsearch_indexing:
-
-========
-Indexing
-========
-
-To make a model searchable, you'll need to add it into the search index. All pages, images and documents are indexed for you, so you can start searching them right away.
-
-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 too so that a user's search query will match on their content. See :ref:`wagtailsearch_indexing_fields` for info on how to do this.
-
-If you have a custom model that you would like to make searchable, see :ref:`wagtailsearch_indexing_models`.
-
-
-.. _wagtailsearch_indexing_update:
-
-
-Updating the index
-==================
-
-If the search index is kept separate from the database (when using Elasticsearch for example), you need to keep them both in sync. There are two ways to do this: using the search signal handlers, or calling the ``update_index`` command periodically. For best speed and reliability, it's best to use both if possible.
-
-
-Signal handlers
----------------
-
-``wagtailsearch`` provides some signal handlers which bind to the save/delete signals of all indexed models. This would automatically add and delete them from all backends you have registered in ``WAGTAILSEARCH_BACKENDS``. These signal handlers are automatically registered when the ``wagtail.search`` app is loaded.
-
-In some cases, you may not want your content to be automatically reindexed and instead rely on the ``update_index`` command for indexing. If you need to disable these signal handlers, use one of the following methods:
-
-Disabling auto update signal handlers for a model
-`````````````````````````````````````````````````
-You can disable the signal handlers for an individual model by adding ``search_auto_update = False`` as an attribute on the model class.
-
-Disabling auto update signal handlers for a search backend/whole site
-`````````````````````````````````````````````````````````````````````
-
-You can disable the signal handlers for a whole search backend by setting the ``AUTO_UPDATE`` setting on the backend to ``False``.
-
-If all search backends have ``AUTO_UPDATE`` set to ``False``, the signal handlers will be completely disabled for the whole site.
-
-For documentation on the ``AUTO_UPDATE`` setting, see :ref:`wagtailsearch_backends_auto_update`.
-
-
-The ``update_index`` command
-----------------------------
-
-Wagtail also provides a command for rebuilding the index from scratch.
-
-:code:`./manage.py update_index`
-
-It is recommended to run this command once a week and at the following times:
-
-- whenever any pages have been created through a script (after an import, for example)
-- whenever any changes have been made to models or search configuration
-
-The search may not return any results while this command is running, so avoid running it at peak times.
-
-.. note::
-
-    The ``update_index`` command is also aliased as ``wagtail_update_index``, for use when another installed package (such as `Haystack <https://haystacksearch.org/>`_) provides a conflicting ``update_index`` command. In this case, the other package's entry in ``INSTALLED_APPS`` should appear above ``wagtail.search`` so that its ``update_index`` command takes precedence over Wagtail's.
-
-
-.. _wagtailsearch_indexing_fields:
-
-Indexing extra fields
-=====================
-
-Fields must be explicitly added to the ``search_fields`` property of your ``Page``-derived model, in order for you to be able to search/filter on them. This is done by overriding ``search_fields`` to append a list of extra ``SearchField``/``FilterField`` objects to it.
-
-
-Example
--------
-
-This creates an ``EventPage`` model with two fields: ``description`` and ``date``. ``description`` is indexed as a ``SearchField`` and ``date`` is indexed as a ``FilterField``
-
-
-.. code-block:: python
-
-    from wagtail.search import index
-    from django.utils import timezone
-
-    class EventPage(Page):
-        description = models.TextField()
-        date = models.DateField()
-
-        search_fields = Page.search_fields + [ # Inherit search_fields from Page
-            index.SearchField('description'),
-            index.FilterField('date'),
-        ]
-
-
-    # Get future events which contain the string "Christmas" in the title or description
-    >>> EventPage.objects.filter(date__gt=timezone.now()).search("Christmas")
-
-
-.. _wagtailsearch_index_searchfield:
-
-``index.SearchField``
----------------------
-
-These are used for performing full-text searches on your models, usually for text fields.
-
-
-Options
-```````
-
-- **partial_match** (``boolean``) - Setting this to true allows results to be matched on parts of words. For example, this is set on the title field by default, so a page titled ``Hello World!`` will be found if the user only types ``Hel`` into the search box.
-- **boost** (``int/float``) - This allows you to set fields as being more important than others. Setting this to a high number on a field will cause pages with matches in that field to be ranked higher. By default, this is set to 2 on the Page title field and 1 on all other fields.
-- **es_extra** (``dict``) - This field is to allow the developer to set or override any setting on the field in the Elasticsearch mapping. Use this if you want to make use of any Elasticsearch features that are not yet supported in Wagtail.
-
-
-.. _wagtailsearch_index_filterfield:
-
-``index.AutocompleteField``
----------------------------
-
-These are used for autocomplete queries which match partial words. For example, a page titled ``Hello World!`` will be found if the user only types ``Hel`` into the search box.
-
-This takes the exact same options as ``index.SearchField`` (with the exception of ``partial_match``, which has no effect).
-
-
-.. tip::
-
-   Only index fields that are displayed in the search results with ``index.AutocompleteField``. This allows users to see any words that were partial-matched on.
-
-
-``index.FilterField``
----------------------
-
-These are added to the search index but are not used for full-text searches. Instead, they allow you to run filters on your search results.
-
-
-.. _wagtailsearch_index_relatedfields:
-
-``index.RelatedFields``
------------------------
-
-This allows you to index fields from related objects. It works on all types of related fields, including their reverse accessors.
-
-For example, if we have a book that has a ``ForeignKey`` to its author, we can nest the author's ``name`` and ``date_of_birth`` fields inside the book:
-
-.. code-block:: python
-
-    from wagtail.search import index
-
-    class Book(models.Model, index.Indexed):
-        ...
-
-        search_fields = [
-            index.SearchField('title'),
-            index.FilterField('published_date'),
-
-            index.RelatedFields('author', [
-                index.SearchField('name'),
-                index.FilterField('date_of_birth'),
-            ]),
-        ]
-
-This will allow you to search for books by their author's name.
-
-It works the other way around as well. You can index an author's books, allowing an author to be searched for by the titles of books they've published:
-
-.. code-block:: python
-
-    from wagtail.search import index
-
-    class Author(models.Model, index.Indexed):
-        ...
-
-        search_fields = [
-            index.SearchField('name'),
-            index.FilterField('date_of_birth'),
-
-            index.RelatedFields('books', [
-                index.SearchField('title'),
-                index.FilterField('published_date'),
-            ]),
-        ]
-
-.. topic:: Filtering on ``index.RelatedFields``
-
-    It's not possible to filter on any ``index.FilterFields`` within ``index.RelatedFields`` using the ``QuerySet`` API. However, the fields are indexed, so it should be possible to use them by querying Elasticsearch manually.
-
-    Filtering on ``index.RelatedFields`` with the ``QuerySet`` API is planned for a future release of Wagtail.
-
-.. _wagtailsearch_indexing_callable_fields:
-
-Indexing callables and other attributes
----------------------------------------
-
-.. note::
-
-    This is not supported in the :ref:`wagtailsearch_backends_database`
-
-
-Search/filter fields do not need to be Django model fields. They can also be any method or attribute on your model class.
-
-One use for this is indexing the ``get_*_display`` methods Django creates automatically for fields with choices.
-
-
-.. code-block:: python
-
-    from wagtail.search import index
-
-    class EventPage(Page):
-        IS_PRIVATE_CHOICES = (
-            (False, "Public"),
-            (True, "Private"),
-        )
-
-        is_private = models.BooleanField(choices=IS_PRIVATE_CHOICES)
-
-        search_fields = Page.search_fields + [
-            # Index the human-readable string for searching.
-            index.SearchField('get_is_private_display'),
-
-            # Index the boolean value for filtering.
-            index.FilterField('is_private'),
-        ]
-
-Callables also provide a way to index fields from related models. In the example from :ref:`inline_panels`, to index each BookPage by the titles of its related_links:
-
-.. code-block:: python
-
-    class BookPage(Page):
-        # ...
-        def get_related_link_titles(self):
-            # Get list of titles and concatenate them
-            return '\n'.join(self.related_links.all().values_list('name', flat=True))
-
-        search_fields = Page.search_fields + [
-            # ...
-            index.SearchField('get_related_link_titles'),
-        ]
-
-.. _wagtailsearch_indexing_models:
-
-Indexing custom models
-======================
-
-Any Django model can be indexed and searched.
-
-To do this, inherit from ``index.Indexed`` and add some ``search_fields`` to the model.
-
-.. code-block:: python
-
-    from wagtail.search import index
-
-    class Book(index.Indexed, models.Model):
-        title = models.CharField(max_length=255)
-        genre = models.CharField(max_length=255, choices=GENRE_CHOICES)
-        author = models.ForeignKey(Author, on_delete=models.CASCADE)
-        published_date = models.DateTimeField()
-
-        search_fields = [
-            index.SearchField('title', partial_match=True, boost=10),
-            index.SearchField('get_genre_display'),
-
-            index.FilterField('genre'),
-            index.FilterField('author'),
-            index.FilterField('published_date'),
-        ]
-
-    # As this model doesn't have a search method in its QuerySet, we have to call search directly on the backend
-    >>> from wagtail.search.backends import get_search_backend
-    >>> s = get_search_backend()
-
-    # Run a search for a book by Roald Dahl
-    >>> roald_dahl = Author.objects.get(name="Roald Dahl")
-    >>> s.search("chocolate factory", Book.objects.filter(author=roald_dahl))
-    [<Book: Charlie and the chocolate factory>]