We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Site search searches on sidebar
Look at this search result: https://developer.mozilla.org/en-US/search?q=django
<img width="726" alt="Screen Shot 2019-12-18 at 8 33 39 AM" src="https://user-images.githubusercontent.com/26739/71090488-2096db80-2171-11ea-999b-a92d470687db.png">

It's showing pages that mention "Django" in an "unexpanded" sidebar. 
That's not helping. It's misleading and if a particular string is in the sidebar, it'll exist on [its own page](https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django). 

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `kuma/wiki/search.py`
Content:
```
1 
2 
3 import logging
4 from math import ceil
5 
6 from celery import chain
7 from django.conf import settings
8 from django.db.models import Q
9 from django.utils.html import strip_tags
10 from django.utils.translation import ugettext_lazy as _
11 from elasticsearch.helpers import bulk
12 from elasticsearch_dsl import document, field
13 from elasticsearch_dsl.connections import connections
14 from elasticsearch_dsl.mapping import Mapping
15 from elasticsearch_dsl.search import Search
16 
17 from kuma.core.utils import chord_flow, chunked
18 
19 from .constants import EXPERIMENT_TITLE_PREFIX
20 
21 
22 log = logging.getLogger('kuma.wiki.search')
23 
24 
25 class WikiDocumentType(document.Document):
26     excerpt_fields = ['summary', 'content']
27     exclude_slugs = ['Talk:', 'User:', 'User_talk:', 'Template_talk:',
28                      'Project_talk:', EXPERIMENT_TITLE_PREFIX]
29 
30     boost = field.Float(null_value=1.0)
31     content = field.Text(analyzer='kuma_content',
32                          term_vector='with_positions_offsets')
33     css_classnames = field.Keyword()
34     html_attributes = field.Keyword()
35     id = field.Long()
36     kumascript_macros = field.Keyword()
37     locale = field.Keyword()
38     modified = field.Date()
39     parent = field.Object(properties={
40         'id': field.Long(),
41         'title': field.Text(analyzer='kuma_title'),
42         'slug': field.Keyword(),
43         'locale': field.Keyword(),
44     })
45     slug = field.Keyword()
46     summary = field.Text(analyzer='kuma_content',
47                          term_vector='with_positions_offsets')
48     tags = field.Keyword()
49     title = field.Text(analyzer='kuma_title')
50 
51     class Meta(object):
52         mapping = Mapping('wiki_document')
53         mapping.meta('_all', enabled=False)
54 
55     @classmethod
56     def get_connection(cls, alias='default'):
57         return connections.get_connection(alias)
58 
59     @classmethod
60     def get_doc_type(cls):
61         return cls._doc_type.name
62 
63     @classmethod
64     def case_insensitive_keywords(cls, keywords):
65         '''Create a unique list of lowercased keywords.'''
66         return sorted({keyword.lower() for keyword in keywords})
67 
68     @classmethod
69     def from_django(cls, obj):
70         is_root_document = obj.slug.count('/') == 1
71         doc = {
72             'id': obj.id,
73             'boost': 4.0 if is_root_document else 1.0,
74             'title': obj.title,
75             'slug': obj.slug,
76             'summary': obj.get_summary_text(),
77             'locale': obj.locale,
78             'modified': obj.modified,
79             'content': strip_tags(obj.rendered_html or ''),
80             'tags': [o.name for o in obj.tags.all()],
81             'kumascript_macros': cls.case_insensitive_keywords(
82                 obj.extract.macro_names()),
83             'css_classnames': cls.case_insensitive_keywords(
84                 obj.extract.css_classnames()),
85             'html_attributes': cls.case_insensitive_keywords(
86                 obj.extract.html_attributes()),
87         }
88 
89         if obj.parent:
90             doc['parent'] = {
91                 'id': obj.parent.id,
92                 'title': obj.parent.title,
93                 'locale': obj.parent.locale,
94                 'slug': obj.parent.slug,
95             }
96         else:
97             doc['parent'] = {}
98 
99         return doc
100 
101     @classmethod
102     def get_mapping(cls):
103         return cls._doc_type.mapping.to_dict()
104 
105     @classmethod
106     def get_analysis(cls):
107         return {
108             'filter': {
109                 'kuma_word_delimiter': {
110                     'type': 'word_delimiter',
111                     'preserve_original': True,  # hi-fi -> hifi, hi-fi
112                     'catenate_words': True,  # hi-fi -> hifi
113                     'catenate_numbers': True,  # 90-210 -> 90210
114                 }
115             },
116             'analyzer': {
117                 'default': {
118                     'tokenizer': 'standard',
119                     'filter': ['standard', 'elision']
120                 },
121                 # a custom analyzer that strips html and uses our own
122                 # word delimiter filter and the elision filter
123                 # (e.g. L'attribut -> attribut). The rest is the same as
124                 # the snowball analyzer
125                 'kuma_content': {
126                     'type': 'custom',
127                     'tokenizer': 'standard',
128                     'char_filter': ['html_strip'],
129                     'filter': [
130                         'elision',
131                         'kuma_word_delimiter',
132                         'lowercase',
133                         'standard',
134                         'stop',
135                         'snowball',
136                     ],
137                 },
138                 'kuma_title': {
139                     'type': 'custom',
140                     'tokenizer': 'standard',
141                     'filter': [
142                         'elision',
143                         'kuma_word_delimiter',
144                         'lowercase',
145                         'standard',
146                         'snowball',
147                     ],
148                 },
149             },
150         }
151 
152     @classmethod
153     def get_settings(cls):
154         return {
155             'mappings': cls.get_mapping(),
156             'settings': {
157                 'analysis': cls.get_analysis(),
158                 'number_of_replicas': settings.ES_DEFAULT_NUM_REPLICAS,
159                 'number_of_shards': settings.ES_DEFAULT_NUM_SHARDS,
160             }
161         }
162 
163     @classmethod
164     def bulk_index(cls, documents, id_field='id', es=None, index=None):
165         """Index of a bunch of documents."""
166         es = es or cls.get_connection()
167         index = index or cls.get_index()
168         type = cls.get_doc_type()
169 
170         actions = [
171             {'_index': index, '_type': type, '_id': d['id'], '_source': d}
172             for d in documents]
173 
174         bulk(es, actions)
175 
176     @classmethod
177     def bulk_delete(cls, ids, es=None, index=None):
178         """Index of a bunch of documents."""
179         es = es or cls.get_connection()
180         index = index or cls.get_index()
181         type = cls.get_doc_type()
182 
183         actions = [
184             {'_op_type': 'delete', '_index': index, '_type': type, '_id': _id}
185             for _id in ids]
186 
187         bulk(es, actions)
188 
189     @classmethod
190     def get_index(cls):
191         from kuma.search.models import Index
192         return Index.objects.get_current().prefixed_name
193 
194     @classmethod
195     def search(cls, **kwargs):
196         options = {
197             'using': connections.get_connection(),
198             'index': cls.get_index(),
199             'doc_type': {cls._doc_type.name: cls.from_es},
200         }
201         options.update(kwargs)
202         sq = Search(**options)
203 
204         return sq
205 
206     @classmethod
207     def get_model(cls):
208         from kuma.wiki.models import Document
209         return Document
210 
211     @classmethod
212     def get_indexable(cls, percent=100):
213         """
214         For this mapping type return a list of model IDs that should be
215         indexed with the management command, in a full reindex.
216 
217         WARNING: When changing this code make sure to update the
218                  ``should_update`` method below, too!
219 
220         """
221         model = cls.get_model()
222 
223         excludes = Q()
224         for exclude in cls.exclude_slugs:
225             excludes |= Q(slug__startswith=exclude)
226 
227         qs = model.objects.filter(is_redirect=False).exclude(excludes)
228 
229         percent = percent / 100
230         if percent < 1:
231             qs = qs[:int(qs.count() * percent)]
232 
233         return qs.values_list('id', flat=True)
234 
235     @classmethod
236     def should_update(cls, obj):
237         """
238         Given a Document instance should return boolean value
239         whether the instance should be indexed or not.
240 
241         WARNING: This *must* mirror the logic of the ``get_indexable``
242                  method above!
243         """
244         return (not obj.is_redirect and not obj.deleted and
245                 not any([exclude in obj.slug
246                          for exclude in cls.exclude_slugs]))
247 
248     def get_excerpt(self):
249         highlighted = getattr(self.meta, 'highlight', None)
250         if highlighted:
251             for excerpt_field in self.excerpt_fields:
252                 if excerpt_field in highlighted:
253                     return '…'.join(highlighted[excerpt_field])
254         return self.summary
255 
256     @classmethod
257     def reindex_all(cls, chunk_size=500, index=None, percent=100):
258         """Rebuild ElasticSearch indexes.
259 
260         :arg chunk_size: how many documents to bulk index as a single chunk.
261         :arg index: the `Index` object to reindex into. Uses the current
262             promoted index if none provided.
263         :arg percent: 1 to 100--the percentage of the db to index.
264 
265         """
266         from kuma.search.models import Index
267         from kuma.search.tasks import prepare_index, finalize_index
268         from kuma.wiki.tasks import index_documents
269 
270         index = index or Index.objects.get_current()
271 
272         # Get the list of document IDs to index.
273         indexable = WikiDocumentType.get_indexable(percent)
274 
275         total = len(indexable)
276         total_chunks = int(ceil(total / chunk_size))
277 
278         pre_task = prepare_index.si(index.pk)
279         post_task = finalize_index.si(index.pk)
280 
281         if not total:
282             # If there's no data we still create the index and finalize it.
283             chain(pre_task, post_task).apply_async()
284         else:
285             index_tasks = [index_documents.si(chunk, index.pk)
286                            for chunk in chunked(indexable, chunk_size)]
287             chord_flow(pre_task, index_tasks, post_task).apply_async()
288 
289         message = _(
290             'Indexing %(total)d documents into %(total_chunks)d chunks of '
291             'size %(size)d into index %(index)s.' % {
292                 'total': total,
293                 'total_chunks': total_chunks,
294                 'size': chunk_size,
295                 'index': index.prefixed_name
296             }
297         )
298         return message
299
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/kuma/wiki/search.py b/kuma/wiki/search.py
--- a/kuma/wiki/search.py
+++ b/kuma/wiki/search.py
@@ -76,7 +76,7 @@
             'summary': obj.get_summary_text(),
             'locale': obj.locale,
             'modified': obj.modified,
-            'content': strip_tags(obj.rendered_html or ''),
+            'content': strip_tags(obj.get_body_html() or ''),
             'tags': [o.name for o in obj.tags.all()],
             'kumascript_macros': cls.case_insensitive_keywords(
                 obj.extract.macro_names()),

{"golden_diff": "diff --git a/kuma/wiki/search.py b/kuma/wiki/search.py\n--- a/kuma/wiki/search.py\n+++ b/kuma/wiki/search.py\n@@ -76,7 +76,7 @@\n             'summary': obj.get_summary_text(),\n             'locale': obj.locale,\n             'modified': obj.modified,\n-            'content': strip_tags(obj.rendered_html or ''),\n+            'content': strip_tags(obj.get_body_html() or ''),\n             'tags': [o.name for o in obj.tags.all()],\n             'kumascript_macros': cls.case_insensitive_keywords(\n                 obj.extract.macro_names()),\n", "issue": "Site search searches on sidebar\nLook at this search result: https://developer.mozilla.org/en-US/search?q=django\r\n<img width=\"726\" alt=\"Screen Shot 2019-12-18 at 8 33 39 AM\" src=\"https://user-images.githubusercontent.com/26739/71090488-2096db80-2171-11ea-999b-a92d470687db.png\">\r\n\r\nIt's showing pages that mention \"Django\" in an \"unexpanded\" sidebar. \r\nThat's not helping. It's misleading and if a particular string is in the sidebar, it'll exist on [its own page](https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django). \n", "before_files": [{"content": "\n\nimport logging\nfrom math import ceil\n\nfrom celery import chain\nfrom django.conf import settings\nfrom django.db.models import Q\nfrom django.utils.html import strip_tags\nfrom django.utils.translation import ugettext_lazy as _\nfrom elasticsearch.helpers import bulk\nfrom elasticsearch_dsl import document, field\nfrom elasticsearch_dsl.connections import connections\nfrom elasticsearch_dsl.mapping import Mapping\nfrom elasticsearch_dsl.search import Search\n\nfrom kuma.core.utils import chord_flow, chunked\n\nfrom .constants import EXPERIMENT_TITLE_PREFIX\n\n\nlog = logging.getLogger('kuma.wiki.search')\n\n\nclass WikiDocumentType(document.Document):\n    excerpt_fields = ['summary', 'content']\n    exclude_slugs = ['Talk:', 'User:', 'User_talk:', 'Template_talk:',\n                     'Project_talk:', EXPERIMENT_TITLE_PREFIX]\n\n    boost = field.Float(null_value=1.0)\n    content = field.Text(analyzer='kuma_content',\n                         term_vector='with_positions_offsets')\n    css_classnames = field.Keyword()\n    html_attributes = field.Keyword()\n    id = field.Long()\n    kumascript_macros = field.Keyword()\n    locale = field.Keyword()\n    modified = field.Date()\n    parent = field.Object(properties={\n        'id': field.Long(),\n        'title': field.Text(analyzer='kuma_title'),\n        'slug': field.Keyword(),\n        'locale': field.Keyword(),\n    })\n    slug = field.Keyword()\n    summary = field.Text(analyzer='kuma_content',\n                         term_vector='with_positions_offsets')\n    tags = field.Keyword()\n    title = field.Text(analyzer='kuma_title')\n\n    class Meta(object):\n        mapping = Mapping('wiki_document')\n        mapping.meta('_all', enabled=False)\n\n    @classmethod\n    def get_connection(cls, alias='default'):\n        return connections.get_connection(alias)\n\n    @classmethod\n    def get_doc_type(cls):\n        return cls._doc_type.name\n\n    @classmethod\n    def case_insensitive_keywords(cls, keywords):\n        '''Create a unique list of lowercased keywords.'''\n        return sorted({keyword.lower() for keyword in keywords})\n\n    @classmethod\n    def from_django(cls, obj):\n        is_root_document = obj.slug.count('/') == 1\n        doc = {\n            'id': obj.id,\n            'boost': 4.0 if is_root_document else 1.0,\n            'title': obj.title,\n            'slug': obj.slug,\n            'summary': obj.get_summary_text(),\n            'locale': obj.locale,\n            'modified': obj.modified,\n            'content': strip_tags(obj.rendered_html or ''),\n            'tags': [o.name for o in obj.tags.all()],\n            'kumascript_macros': cls.case_insensitive_keywords(\n                obj.extract.macro_names()),\n            'css_classnames': cls.case_insensitive_keywords(\n                obj.extract.css_classnames()),\n            'html_attributes': cls.case_insensitive_keywords(\n                obj.extract.html_attributes()),\n        }\n\n        if obj.parent:\n            doc['parent'] = {\n                'id': obj.parent.id,\n                'title': obj.parent.title,\n                'locale': obj.parent.locale,\n                'slug': obj.parent.slug,\n            }\n        else:\n            doc['parent'] = {}\n\n        return doc\n\n    @classmethod\n    def get_mapping(cls):\n        return cls._doc_type.mapping.to_dict()\n\n    @classmethod\n    def get_analysis(cls):\n        return {\n            'filter': {\n                'kuma_word_delimiter': {\n                    'type': 'word_delimiter',\n                    'preserve_original': True,  # hi-fi -> hifi, hi-fi\n                    'catenate_words': True,  # hi-fi -> hifi\n                    'catenate_numbers': True,  # 90-210 -> 90210\n                }\n            },\n            'analyzer': {\n                'default': {\n                    'tokenizer': 'standard',\n                    'filter': ['standard', 'elision']\n                },\n                # a custom analyzer that strips html and uses our own\n                # word delimiter filter and the elision filter\n                # (e.g. L'attribut -> attribut). The rest is the same as\n                # the snowball analyzer\n                'kuma_content': {\n                    'type': 'custom',\n                    'tokenizer': 'standard',\n                    'char_filter': ['html_strip'],\n                    'filter': [\n                        'elision',\n                        'kuma_word_delimiter',\n                        'lowercase',\n                        'standard',\n                        'stop',\n                        'snowball',\n                    ],\n                },\n                'kuma_title': {\n                    'type': 'custom',\n                    'tokenizer': 'standard',\n                    'filter': [\n                        'elision',\n                        'kuma_word_delimiter',\n                        'lowercase',\n                        'standard',\n                        'snowball',\n                    ],\n                },\n            },\n        }\n\n    @classmethod\n    def get_settings(cls):\n        return {\n            'mappings': cls.get_mapping(),\n            'settings': {\n                'analysis': cls.get_analysis(),\n                'number_of_replicas': settings.ES_DEFAULT_NUM_REPLICAS,\n                'number_of_shards': settings.ES_DEFAULT_NUM_SHARDS,\n            }\n        }\n\n    @classmethod\n    def bulk_index(cls, documents, id_field='id', es=None, index=None):\n        \"\"\"Index of a bunch of documents.\"\"\"\n        es = es or cls.get_connection()\n        index = index or cls.get_index()\n        type = cls.get_doc_type()\n\n        actions = [\n            {'_index': index, '_type': type, '_id': d['id'], '_source': d}\n            for d in documents]\n\n        bulk(es, actions)\n\n    @classmethod\n    def bulk_delete(cls, ids, es=None, index=None):\n        \"\"\"Index of a bunch of documents.\"\"\"\n        es = es or cls.get_connection()\n        index = index or cls.get_index()\n        type = cls.get_doc_type()\n\n        actions = [\n            {'_op_type': 'delete', '_index': index, '_type': type, '_id': _id}\n            for _id in ids]\n\n        bulk(es, actions)\n\n    @classmethod\n    def get_index(cls):\n        from kuma.search.models import Index\n        return Index.objects.get_current().prefixed_name\n\n    @classmethod\n    def search(cls, **kwargs):\n        options = {\n            'using': connections.get_connection(),\n            'index': cls.get_index(),\n            'doc_type': {cls._doc_type.name: cls.from_es},\n        }\n        options.update(kwargs)\n        sq = Search(**options)\n\n        return sq\n\n    @classmethod\n    def get_model(cls):\n        from kuma.wiki.models import Document\n        return Document\n\n    @classmethod\n    def get_indexable(cls, percent=100):\n        \"\"\"\n        For this mapping type return a list of model IDs that should be\n        indexed with the management command, in a full reindex.\n\n        WARNING: When changing this code make sure to update the\n                 ``should_update`` method below, too!\n\n        \"\"\"\n        model = cls.get_model()\n\n        excludes = Q()\n        for exclude in cls.exclude_slugs:\n            excludes |= Q(slug__startswith=exclude)\n\n        qs = model.objects.filter(is_redirect=False).exclude(excludes)\n\n        percent = percent / 100\n        if percent < 1:\n            qs = qs[:int(qs.count() * percent)]\n\n        return qs.values_list('id', flat=True)\n\n    @classmethod\n    def should_update(cls, obj):\n        \"\"\"\n        Given a Document instance should return boolean value\n        whether the instance should be indexed or not.\n\n        WARNING: This *must* mirror the logic of the ``get_indexable``\n                 method above!\n        \"\"\"\n        return (not obj.is_redirect and not obj.deleted and\n                not any([exclude in obj.slug\n                         for exclude in cls.exclude_slugs]))\n\n    def get_excerpt(self):\n        highlighted = getattr(self.meta, 'highlight', None)\n        if highlighted:\n            for excerpt_field in self.excerpt_fields:\n                if excerpt_field in highlighted:\n                    return '\u2026'.join(highlighted[excerpt_field])\n        return self.summary\n\n    @classmethod\n    def reindex_all(cls, chunk_size=500, index=None, percent=100):\n        \"\"\"Rebuild ElasticSearch indexes.\n\n        :arg chunk_size: how many documents to bulk index as a single chunk.\n        :arg index: the `Index` object to reindex into. Uses the current\n            promoted index if none provided.\n        :arg percent: 1 to 100--the percentage of the db to index.\n\n        \"\"\"\n        from kuma.search.models import Index\n        from kuma.search.tasks import prepare_index, finalize_index\n        from kuma.wiki.tasks import index_documents\n\n        index = index or Index.objects.get_current()\n\n        # Get the list of document IDs to index.\n        indexable = WikiDocumentType.get_indexable(percent)\n\n        total = len(indexable)\n        total_chunks = int(ceil(total / chunk_size))\n\n        pre_task = prepare_index.si(index.pk)\n        post_task = finalize_index.si(index.pk)\n\n        if not total:\n            # If there's no data we still create the index and finalize it.\n            chain(pre_task, post_task).apply_async()\n        else:\n            index_tasks = [index_documents.si(chunk, index.pk)\n                           for chunk in chunked(indexable, chunk_size)]\n            chord_flow(pre_task, index_tasks, post_task).apply_async()\n\n        message = _(\n            'Indexing %(total)d documents into %(total_chunks)d chunks of '\n            'size %(size)d into index %(index)s.' % {\n                'total': total,\n                'total_chunks': total_chunks,\n                'size': chunk_size,\n                'index': index.prefixed_name\n            }\n        )\n        return message\n", "path": "kuma/wiki/search.py"}], "after_files": [{"content": "\n\nimport logging\nfrom math import ceil\n\nfrom celery import chain\nfrom django.conf import settings\nfrom django.db.models import Q\nfrom django.utils.html import strip_tags\nfrom django.utils.translation import ugettext_lazy as _\nfrom elasticsearch.helpers import bulk\nfrom elasticsearch_dsl import document, field\nfrom elasticsearch_dsl.connections import connections\nfrom elasticsearch_dsl.mapping import Mapping\nfrom elasticsearch_dsl.search import Search\n\nfrom kuma.core.utils import chord_flow, chunked\n\nfrom .constants import EXPERIMENT_TITLE_PREFIX\n\n\nlog = logging.getLogger('kuma.wiki.search')\n\n\nclass WikiDocumentType(document.Document):\n    excerpt_fields = ['summary', 'content']\n    exclude_slugs = ['Talk:', 'User:', 'User_talk:', 'Template_talk:',\n                     'Project_talk:', EXPERIMENT_TITLE_PREFIX]\n\n    boost = field.Float(null_value=1.0)\n    content = field.Text(analyzer='kuma_content',\n                         term_vector='with_positions_offsets')\n    css_classnames = field.Keyword()\n    html_attributes = field.Keyword()\n    id = field.Long()\n    kumascript_macros = field.Keyword()\n    locale = field.Keyword()\n    modified = field.Date()\n    parent = field.Object(properties={\n        'id': field.Long(),\n        'title': field.Text(analyzer='kuma_title'),\n        'slug': field.Keyword(),\n        'locale': field.Keyword(),\n    })\n    slug = field.Keyword()\n    summary = field.Text(analyzer='kuma_content',\n                         term_vector='with_positions_offsets')\n    tags = field.Keyword()\n    title = field.Text(analyzer='kuma_title')\n\n    class Meta(object):\n        mapping = Mapping('wiki_document')\n        mapping.meta('_all', enabled=False)\n\n    @classmethod\n    def get_connection(cls, alias='default'):\n        return connections.get_connection(alias)\n\n    @classmethod\n    def get_doc_type(cls):\n        return cls._doc_type.name\n\n    @classmethod\n    def case_insensitive_keywords(cls, keywords):\n        '''Create a unique list of lowercased keywords.'''\n        return sorted({keyword.lower() for keyword in keywords})\n\n    @classmethod\n    def from_django(cls, obj):\n        is_root_document = obj.slug.count('/') == 1\n        doc = {\n            'id': obj.id,\n            'boost': 4.0 if is_root_document else 1.0,\n            'title': obj.title,\n            'slug': obj.slug,\n            'summary': obj.get_summary_text(),\n            'locale': obj.locale,\n            'modified': obj.modified,\n            'content': strip_tags(obj.get_body_html() or ''),\n            'tags': [o.name for o in obj.tags.all()],\n            'kumascript_macros': cls.case_insensitive_keywords(\n                obj.extract.macro_names()),\n            'css_classnames': cls.case_insensitive_keywords(\n                obj.extract.css_classnames()),\n            'html_attributes': cls.case_insensitive_keywords(\n                obj.extract.html_attributes()),\n        }\n\n        if obj.parent:\n            doc['parent'] = {\n                'id': obj.parent.id,\n                'title': obj.parent.title,\n                'locale': obj.parent.locale,\n                'slug': obj.parent.slug,\n            }\n        else:\n            doc['parent'] = {}\n\n        return doc\n\n    @classmethod\n    def get_mapping(cls):\n        return cls._doc_type.mapping.to_dict()\n\n    @classmethod\n    def get_analysis(cls):\n        return {\n            'filter': {\n                'kuma_word_delimiter': {\n                    'type': 'word_delimiter',\n                    'preserve_original': True,  # hi-fi -> hifi, hi-fi\n                    'catenate_words': True,  # hi-fi -> hifi\n                    'catenate_numbers': True,  # 90-210 -> 90210\n                }\n            },\n            'analyzer': {\n                'default': {\n                    'tokenizer': 'standard',\n                    'filter': ['standard', 'elision']\n                },\n                # a custom analyzer that strips html and uses our own\n                # word delimiter filter and the elision filter\n                # (e.g. L'attribut -> attribut). The rest is the same as\n                # the snowball analyzer\n                'kuma_content': {\n                    'type': 'custom',\n                    'tokenizer': 'standard',\n                    'char_filter': ['html_strip'],\n                    'filter': [\n                        'elision',\n                        'kuma_word_delimiter',\n                        'lowercase',\n                        'standard',\n                        'stop',\n                        'snowball',\n                    ],\n                },\n                'kuma_title': {\n                    'type': 'custom',\n                    'tokenizer': 'standard',\n                    'filter': [\n                        'elision',\n                        'kuma_word_delimiter',\n                        'lowercase',\n                        'standard',\n                        'snowball',\n                    ],\n                },\n            },\n        }\n\n    @classmethod\n    def get_settings(cls):\n        return {\n            'mappings': cls.get_mapping(),\n            'settings': {\n                'analysis': cls.get_analysis(),\n                'number_of_replicas': settings.ES_DEFAULT_NUM_REPLICAS,\n                'number_of_shards': settings.ES_DEFAULT_NUM_SHARDS,\n            }\n        }\n\n    @classmethod\n    def bulk_index(cls, documents, id_field='id', es=None, index=None):\n        \"\"\"Index of a bunch of documents.\"\"\"\n        es = es or cls.get_connection()\n        index = index or cls.get_index()\n        type = cls.get_doc_type()\n\n        actions = [\n            {'_index': index, '_type': type, '_id': d['id'], '_source': d}\n            for d in documents]\n\n        bulk(es, actions)\n\n    @classmethod\n    def bulk_delete(cls, ids, es=None, index=None):\n        \"\"\"Index of a bunch of documents.\"\"\"\n        es = es or cls.get_connection()\n        index = index or cls.get_index()\n        type = cls.get_doc_type()\n\n        actions = [\n            {'_op_type': 'delete', '_index': index, '_type': type, '_id': _id}\n            for _id in ids]\n\n        bulk(es, actions)\n\n    @classmethod\n    def get_index(cls):\n        from kuma.search.models import Index\n        return Index.objects.get_current().prefixed_name\n\n    @classmethod\n    def search(cls, **kwargs):\n        options = {\n            'using': connections.get_connection(),\n            'index': cls.get_index(),\n            'doc_type': {cls._doc_type.name: cls.from_es},\n        }\n        options.update(kwargs)\n        sq = Search(**options)\n\n        return sq\n\n    @classmethod\n    def get_model(cls):\n        from kuma.wiki.models import Document\n        return Document\n\n    @classmethod\n    def get_indexable(cls, percent=100):\n        \"\"\"\n        For this mapping type return a list of model IDs that should be\n        indexed with the management command, in a full reindex.\n\n        WARNING: When changing this code make sure to update the\n                 ``should_update`` method below, too!\n\n        \"\"\"\n        model = cls.get_model()\n\n        excludes = Q()\n        for exclude in cls.exclude_slugs:\n            excludes |= Q(slug__startswith=exclude)\n\n        qs = model.objects.filter(is_redirect=False).exclude(excludes)\n\n        percent = percent / 100\n        if percent < 1:\n            qs = qs[:int(qs.count() * percent)]\n\n        return qs.values_list('id', flat=True)\n\n    @classmethod\n    def should_update(cls, obj):\n        \"\"\"\n        Given a Document instance should return boolean value\n        whether the instance should be indexed or not.\n\n        WARNING: This *must* mirror the logic of the ``get_indexable``\n                 method above!\n        \"\"\"\n        return (not obj.is_redirect and not obj.deleted and\n                not any([exclude in obj.slug\n                         for exclude in cls.exclude_slugs]))\n\n    def get_excerpt(self):\n        highlighted = getattr(self.meta, 'highlight', None)\n        if highlighted:\n            for excerpt_field in self.excerpt_fields:\n                if excerpt_field in highlighted:\n                    return '\u2026'.join(highlighted[excerpt_field])\n        return self.summary\n\n    @classmethod\n    def reindex_all(cls, chunk_size=500, index=None, percent=100):\n        \"\"\"Rebuild ElasticSearch indexes.\n\n        :arg chunk_size: how many documents to bulk index as a single chunk.\n        :arg index: the `Index` object to reindex into. Uses the current\n            promoted index if none provided.\n        :arg percent: 1 to 100--the percentage of the db to index.\n\n        \"\"\"\n        from kuma.search.models import Index\n        from kuma.search.tasks import prepare_index, finalize_index\n        from kuma.wiki.tasks import index_documents\n\n        index = index or Index.objects.get_current()\n\n        # Get the list of document IDs to index.\n        indexable = WikiDocumentType.get_indexable(percent)\n\n        total = len(indexable)\n        total_chunks = int(ceil(total / chunk_size))\n\n        pre_task = prepare_index.si(index.pk)\n        post_task = finalize_index.si(index.pk)\n\n        if not total:\n            # If there's no data we still create the index and finalize it.\n            chain(pre_task, post_task).apply_async()\n        else:\n            index_tasks = [index_documents.si(chunk, index.pk)\n                           for chunk in chunked(indexable, chunk_size)]\n            chord_flow(pre_task, index_tasks, post_task).apply_async()\n\n        message = _(\n            'Indexing %(total)d documents into %(total_chunks)d chunks of '\n            'size %(size)d into index %(index)s.' % {\n                'total': total,\n                'total_chunks': total_chunks,\n                'size': chunk_size,\n                'index': index.prefixed_name\n            }\n        )\n        return message\n", "path": "kuma/wiki/search.py"}]}