prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Make /$locale/search.json redirect
  **Summary**
  We get a lot of `NoReverseMatch` on URLs like https://developer.mozilla.org/en-US/search.json
  That endpoint disappeared when we switched to the new search API. 
  Let's make it redirect. 
  
  
  **Additional context**
  https://sentry.prod.mozaws.net/operations/mdn-prod/issues/10482841/
  
  
  --- 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/search/views.py`
  Content:
  ```
  1 from urllib.parse import parse_qs, urlencode
  2 
  3 from django.shortcuts import render
  4 from django.urls import reverse_lazy
  5 from django.views.decorators.cache import never_cache
  6 from django.views.decorators.http import require_GET
  7 from django.views.generic import RedirectView
  8 from ratelimit.decorators import ratelimit
  9 
  10 from kuma.api.v1.search import search as search_api
  11 from kuma.core.decorators import shared_cache_control
  12 
  13 
  14 # Since the search endpoint accepts user input (via query parameters) and its
  15 # response is compressed, use rate limiting to mitigate the BREACH attack
  16 # (see http://breachattack.com/). It still needs to allow a user to click
  17 # the filter switches (bug 1426968).
  18 # Alternate: forbid gzip by setting Content-Encoding: identity
  19 @never_cache
  20 @require_GET
  21 @ratelimit(key="user_or_ip", rate="25/m", block=True)
  22 def search(request, *args, **kwargs):
  23     """
  24     The search view.
  25 
  26     --2021-- THIS VIEW IS A HACK! --2021--
  27     This Django view exists to server-side render the search results page.
  28     But we're moving the search result page to Yari and that one will use a XHR
  29     request (to /api/v1/search) from a skeleton page (aka. SPA).
  30     But as a way to get to that, we need to transition from the old to the new.
  31     So, this page uses the Django view in kuma.api.v1.search.search, which
  32     returns a special `JsonResponse` instance whose data we can pluck out
  33     to our needs for this old view.
  34     Once we've fully moved to the Yari (static + XHR to v1 API) site-search,
  35     we can comfortably delete this view.
  36     """
  37     # The underlying v1 API supports searching without a 'q' but the web
  38     # UI doesn't. For example, the search input field requires a value.
  39     # So we match that here too.
  40     if not request.GET.get("q", "").strip():
  41         status = 400
  42         context = {"results": {}}
  43     else:
  44         # TODO consider, if the current locale is *not* en-US, that we force
  45         # it to do a search in both locales.
  46         # This might come in handy for people searching in a locale where
  47         # there's very little results but they'd be happy to get the en-US ones.
  48         response = search_api(request, *args, **kwargs)
  49         results = response.data
  50 
  51         error = None
  52         status = response.status_code
  53 
  54         # Determine if there were validation errors
  55         if status == 400:
  56             error = ""
  57             for key, messages in results["errors"].items():
  58                 for message in messages:
  59                     error += f"{key}: {message['message']}\n"
  60         else:
  61             # Have to rearrange the 'results' in a way the old search expects it.
  62             # ...which is as follows:
  63             #  - `count`: integer number of matched documents
  64             #  - `previous`: a URL or empty string
  65             #  - `next`: a URL or empty string
  66             #  - `query`: string
  67             #  - `start`: pagination number
  68             #  - `end`: pagination number
  69             #  - `documents`:
  70             #      - `title`
  71             #      - `locale`
  72             #      - `slug`
  73             #      - `excerpt`: string of safe HTML
  74             next_url = ""
  75             previous_url = ""
  76             page = results["metadata"]["page"]
  77             size = results["metadata"]["size"]
  78             count = results["metadata"]["total"]["value"]
  79             query_string = request.META.get("QUERY_STRING")
  80             query_string_parsed = parse_qs(query_string)
  81             if (page + 1) * size < count:
  82                 query_string_parsed["page"] = f"{page + 1}"
  83                 next_url = f"?{urlencode(query_string_parsed, True)}"
  84             if page > 1:
  85                 if page == 2:
  86                     del query_string_parsed["page"]
  87                 else:
  88                     query_string_parsed["page"] = f"{page - 1}"
  89                 previous_url = f"?{urlencode(query_string_parsed, True)}"
  90 
  91             results = {
  92                 "count": count,
  93                 "next": next_url,
  94                 "previous": previous_url,
  95                 "query": request.GET.get("q"),
  96                 "start": (page - 1) * size + 1,
  97                 "end": page * size,
  98                 "documents": [
  99                     {
  100                         "title": x["title"],
  101                         "slug": x["slug"],
  102                         "locale": x["locale"],
  103                         "excerpt": "<br>".join(x["highlight"].get("body", [])),
  104                     }
  105                     for x in results["documents"]
  106                 ],
  107             }
  108 
  109         context = {"results": {"results": None if error else results, "error": error}}
  110     return render(request, "search/react.html", context, status=status)
  111 
  112 
  113 class SearchRedirectView(RedirectView):
  114     permanent = True
  115 
  116     def get_redirect_url(self, *args, **kwargs):
  117         query_string = self.request.META.get("QUERY_STRING")
  118         url = reverse_lazy(
  119             "api.v1.search", kwargs={"locale": self.request.LANGUAGE_CODE}
  120         )
  121         if query_string:
  122             url += "?" + query_string
  123         return url
  124 
  125 
  126 @shared_cache_control(s_maxage=60 * 60 * 24 * 7)
  127 def plugin(request):
  128     """Render an OpenSearch Plugin."""
  129     return render(
  130         request,
  131         "search/plugin.html",
  132         {"locale": request.LANGUAGE_CODE},
  133         content_type="application/opensearchdescription+xml",
  134     )
  135
  ```
  --- 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()
  
  ```
  
golden_diff: diff --git a/kuma/search/views.py b/kuma/search/views.py
  --- a/kuma/search/views.py
  +++ b/kuma/search/views.py
  @@ -1,5 +1,6 @@
   from urllib.parse import parse_qs, urlencode
   
  +from django.conf import settings
   from django.shortcuts import render
   from django.urls import reverse_lazy
   from django.views.decorators.cache import never_cache
  @@ -115,11 +116,14 @@
   
       def get_redirect_url(self, *args, **kwargs):
           query_string = self.request.META.get("QUERY_STRING")
  -        url = reverse_lazy(
  -            "api.v1.search", kwargs={"locale": self.request.LANGUAGE_CODE}
  -        )
  -        if query_string:
  -            url += "?" + query_string
  +        url = reverse_lazy("api.v1.search")
  +        qs = parse_qs(query_string)
  +        # If you used `/en-Us/search.json` you can skip the `?locale=`
  +        # because the default locale in `/api/v1/search` is `en-US`.
  +        if self.request.LANGUAGE_CODE.lower() != settings.LANGUAGE_CODE.lower():
  +            qs["locale"] = self.request.LANGUAGE_CODE
  +        if qs:
  +            url += "?" + urlencode(qs, True)
           return url
  
verification_info: {"golden_diff": "diff --git a/kuma/search/views.py b/kuma/search/views.py\n--- a/kuma/search/views.py\n+++ b/kuma/search/views.py\n@@ -1,5 +1,6 @@\n from urllib.parse import parse_qs, urlencode\n \n+from django.conf import settings\n from django.shortcuts import render\n from django.urls import reverse_lazy\n from django.views.decorators.cache import never_cache\n@@ -115,11 +116,14 @@\n \n     def get_redirect_url(self, *args, **kwargs):\n         query_string = self.request.META.get(\"QUERY_STRING\")\n-        url = reverse_lazy(\n-            \"api.v1.search\", kwargs={\"locale\": self.request.LANGUAGE_CODE}\n-        )\n-        if query_string:\n-            url += \"?\" + query_string\n+        url = reverse_lazy(\"api.v1.search\")\n+        qs = parse_qs(query_string)\n+        # If you used `/en-Us/search.json` you can skip the `?locale=`\n+        # because the default locale in `/api/v1/search` is `en-US`.\n+        if self.request.LANGUAGE_CODE.lower() != settings.LANGUAGE_CODE.lower():\n+            qs[\"locale\"] = self.request.LANGUAGE_CODE\n+        if qs:\n+            url += \"?\" + urlencode(qs, True)\n         return url\n", "issue": "Make /$locale/search.json redirect\n**Summary**\r\nWe get a lot of `NoReverseMatch` on URLs like https://developer.mozilla.org/en-US/search.json\r\nThat endpoint disappeared when we switched to the new search API. \r\nLet's make it redirect. \r\n\r\n\r\n**Additional context**\r\nhttps://sentry.prod.mozaws.net/operations/mdn-prod/issues/10482841/\r\n\n", "before_files": [{"content": "from urllib.parse import parse_qs, urlencode\n\nfrom django.shortcuts import render\nfrom django.urls import reverse_lazy\nfrom django.views.decorators.cache import never_cache\nfrom django.views.decorators.http import require_GET\nfrom django.views.generic import RedirectView\nfrom ratelimit.decorators import ratelimit\n\nfrom kuma.api.v1.search import search as search_api\nfrom kuma.core.decorators import shared_cache_control\n\n\n# Since the search endpoint accepts user input (via query parameters) and its\n# response is compressed, use rate limiting to mitigate the BREACH attack\n# (see http://breachattack.com/). It still needs to allow a user to click\n# the filter switches (bug 1426968).\n# Alternate: forbid gzip by setting Content-Encoding: identity\n@never_cache\n@require_GET\n@ratelimit(key=\"user_or_ip\", rate=\"25/m\", block=True)\ndef search(request, *args, **kwargs):\n    \"\"\"\n    The search view.\n\n    --2021-- THIS VIEW IS A HACK! --2021--\n    This Django view exists to server-side render the search results page.\n    But we're moving the search result page to Yari and that one will use a XHR\n    request (to /api/v1/search) from a skeleton page (aka. SPA).\n    But as a way to get to that, we need to transition from the old to the new.\n    So, this page uses the Django view in kuma.api.v1.search.search, which\n    returns a special `JsonResponse` instance whose data we can pluck out\n    to our needs for this old view.\n    Once we've fully moved to the Yari (static + XHR to v1 API) site-search,\n    we can comfortably delete this view.\n    \"\"\"\n    # The underlying v1 API supports searching without a 'q' but the web\n    # UI doesn't. For example, the search input field requires a value.\n    # So we match that here too.\n    if not request.GET.get(\"q\", \"\").strip():\n        status = 400\n        context = {\"results\": {}}\n    else:\n        # TODO consider, if the current locale is *not* en-US, that we force\n        # it to do a search in both locales.\n        # This might come in handy for people searching in a locale where\n        # there's very little results but they'd be happy to get the en-US ones.\n        response = search_api(request, *args, **kwargs)\n        results = response.data\n\n        error = None\n        status = response.status_code\n\n        # Determine if there were validation errors\n        if status == 400:\n            error = \"\"\n            for key, messages in results[\"errors\"].items():\n                for message in messages:\n                    error += f\"{key}: {message['message']}\\n\"\n        else:\n            # Have to rearrange the 'results' in a way the old search expects it.\n            # ...which is as follows:\n            #  - `count`: integer number of matched documents\n            #  - `previous`: a URL or empty string\n            #  - `next`: a URL or empty string\n            #  - `query`: string\n            #  - `start`: pagination number\n            #  - `end`: pagination number\n            #  - `documents`:\n            #      - `title`\n            #      - `locale`\n            #      - `slug`\n            #      - `excerpt`: string of safe HTML\n            next_url = \"\"\n            previous_url = \"\"\n            page = results[\"metadata\"][\"page\"]\n            size = results[\"metadata\"][\"size\"]\n            count = results[\"metadata\"][\"total\"][\"value\"]\n            query_string = request.META.get(\"QUERY_STRING\")\n            query_string_parsed = parse_qs(query_string)\n            if (page + 1) * size < count:\n                query_string_parsed[\"page\"] = f\"{page + 1}\"\n                next_url = f\"?{urlencode(query_string_parsed, True)}\"\n            if page > 1:\n                if page == 2:\n                    del query_string_parsed[\"page\"]\n                else:\n                    query_string_parsed[\"page\"] = f\"{page - 1}\"\n                previous_url = f\"?{urlencode(query_string_parsed, True)}\"\n\n            results = {\n                \"count\": count,\n                \"next\": next_url,\n                \"previous\": previous_url,\n                \"query\": request.GET.get(\"q\"),\n                \"start\": (page - 1) * size + 1,\n                \"end\": page * size,\n                \"documents\": [\n                    {\n                        \"title\": x[\"title\"],\n                        \"slug\": x[\"slug\"],\n                        \"locale\": x[\"locale\"],\n                        \"excerpt\": \"<br>\".join(x[\"highlight\"].get(\"body\", [])),\n                    }\n                    for x in results[\"documents\"]\n                ],\n            }\n\n        context = {\"results\": {\"results\": None if error else results, \"error\": error}}\n    return render(request, \"search/react.html\", context, status=status)\n\n\nclass SearchRedirectView(RedirectView):\n    permanent = True\n\n    def get_redirect_url(self, *args, **kwargs):\n        query_string = self.request.META.get(\"QUERY_STRING\")\n        url = reverse_lazy(\n            \"api.v1.search\", kwargs={\"locale\": self.request.LANGUAGE_CODE}\n        )\n        if query_string:\n            url += \"?\" + query_string\n        return url\n\n\n@shared_cache_control(s_maxage=60 * 60 * 24 * 7)\ndef plugin(request):\n    \"\"\"Render an OpenSearch Plugin.\"\"\"\n    return render(\n        request,\n        \"search/plugin.html\",\n        {\"locale\": request.LANGUAGE_CODE},\n        content_type=\"application/opensearchdescription+xml\",\n    )\n", "path": "kuma/search/views.py"}], "after_files": [{"content": "from urllib.parse import parse_qs, urlencode\n\nfrom django.conf import settings\nfrom django.shortcuts import render\nfrom django.urls import reverse_lazy\nfrom django.views.decorators.cache import never_cache\nfrom django.views.decorators.http import require_GET\nfrom django.views.generic import RedirectView\nfrom ratelimit.decorators import ratelimit\n\nfrom kuma.api.v1.search import search as search_api\nfrom kuma.core.decorators import shared_cache_control\n\n\n# Since the search endpoint accepts user input (via query parameters) and its\n# response is compressed, use rate limiting to mitigate the BREACH attack\n# (see http://breachattack.com/). It still needs to allow a user to click\n# the filter switches (bug 1426968).\n# Alternate: forbid gzip by setting Content-Encoding: identity\n@never_cache\n@require_GET\n@ratelimit(key=\"user_or_ip\", rate=\"25/m\", block=True)\ndef search(request, *args, **kwargs):\n    \"\"\"\n    The search view.\n\n    --2021-- THIS VIEW IS A HACK! --2021--\n    This Django view exists to server-side render the search results page.\n    But we're moving the search result page to Yari and that one will use a XHR\n    request (to /api/v1/search) from a skeleton page (aka. SPA).\n    But as a way to get to that, we need to transition from the old to the new.\n    So, this page uses the Django view in kuma.api.v1.search.search, which\n    returns a special `JsonResponse` instance whose data we can pluck out\n    to our needs for this old view.\n    Once we've fully moved to the Yari (static + XHR to v1 API) site-search,\n    we can comfortably delete this view.\n    \"\"\"\n    # The underlying v1 API supports searching without a 'q' but the web\n    # UI doesn't. For example, the search input field requires a value.\n    # So we match that here too.\n    if not request.GET.get(\"q\", \"\").strip():\n        status = 400\n        context = {\"results\": {}}\n    else:\n        # TODO consider, if the current locale is *not* en-US, that we force\n        # it to do a search in both locales.\n        # This might come in handy for people searching in a locale where\n        # there's very little results but they'd be happy to get the en-US ones.\n        response = search_api(request, *args, **kwargs)\n        results = response.data\n\n        error = None\n        status = response.status_code\n\n        # Determine if there were validation errors\n        if status == 400:\n            error = \"\"\n            for key, messages in results[\"errors\"].items():\n                for message in messages:\n                    error += f\"{key}: {message['message']}\\n\"\n        else:\n            # Have to rearrange the 'results' in a way the old search expects it.\n            # ...which is as follows:\n            #  - `count`: integer number of matched documents\n            #  - `previous`: a URL or empty string\n            #  - `next`: a URL or empty string\n            #  - `query`: string\n            #  - `start`: pagination number\n            #  - `end`: pagination number\n            #  - `documents`:\n            #      - `title`\n            #      - `locale`\n            #      - `slug`\n            #      - `excerpt`: string of safe HTML\n            next_url = \"\"\n            previous_url = \"\"\n            page = results[\"metadata\"][\"page\"]\n            size = results[\"metadata\"][\"size\"]\n            count = results[\"metadata\"][\"total\"][\"value\"]\n            query_string = request.META.get(\"QUERY_STRING\")\n            query_string_parsed = parse_qs(query_string)\n            if (page + 1) * size < count:\n                query_string_parsed[\"page\"] = f\"{page + 1}\"\n                next_url = f\"?{urlencode(query_string_parsed, True)}\"\n            if page > 1:\n                if page == 2:\n                    del query_string_parsed[\"page\"]\n                else:\n                    query_string_parsed[\"page\"] = f\"{page - 1}\"\n                previous_url = f\"?{urlencode(query_string_parsed, True)}\"\n\n            results = {\n                \"count\": count,\n                \"next\": next_url,\n                \"previous\": previous_url,\n                \"query\": request.GET.get(\"q\"),\n                \"start\": (page - 1) * size + 1,\n                \"end\": page * size,\n                \"documents\": [\n                    {\n                        \"title\": x[\"title\"],\n                        \"slug\": x[\"slug\"],\n                        \"locale\": x[\"locale\"],\n                        \"excerpt\": \"<br>\".join(x[\"highlight\"].get(\"body\", [])),\n                    }\n                    for x in results[\"documents\"]\n                ],\n            }\n\n        context = {\"results\": {\"results\": None if error else results, \"error\": error}}\n    return render(request, \"search/react.html\", context, status=status)\n\n\nclass SearchRedirectView(RedirectView):\n    permanent = True\n\n    def get_redirect_url(self, *args, **kwargs):\n        query_string = self.request.META.get(\"QUERY_STRING\")\n        url = reverse_lazy(\"api.v1.search\")\n        qs = parse_qs(query_string)\n        # If you used `/en-Us/search.json` you can skip the `?locale=`\n        # because the default locale in `/api/v1/search` is `en-US`.\n        if self.request.LANGUAGE_CODE.lower() != settings.LANGUAGE_CODE.lower():\n            qs[\"locale\"] = self.request.LANGUAGE_CODE\n        if qs:\n            url += \"?\" + urlencode(qs, True)\n        return url\n\n\n@shared_cache_control(s_maxage=60 * 60 * 24 * 7)\ndef plugin(request):\n    \"\"\"Render an OpenSearch Plugin.\"\"\"\n    return render(\n        request,\n        \"search/plugin.html\",\n        {\"locale\": request.LANGUAGE_CODE},\n        content_type=\"application/opensearchdescription+xml\",\n    )\n", "path": "kuma/search/views.py"}]}