prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Plone does not redirect ajax calls
  ## BUG/PROBLEM REPORT (OR OTHER COMMON ISSUE)
  
  ### What I did:
  
  1. Create a document with id "a"
  2. Rename it to "b"
  3. Run `curl -sI 'http://localhost:8080/Plone/a/@@getVocabulary'  -H 'Authorization: Basic YOURB64'  -H 'Accept: text/html'`: you will get a redirect (301) + some not useful text ignored by the browser
  4. Run `curl -sI 'http://localhost:8080/Plone/a/@@getVocabulary'  -H 'Authorization: Basic YOURB64'  -H 'Accept: application/json'` you will get a not found json text (status 404)
  
  Note that the two commands differ only for the `Accept` header.
  
  ### What I expect to happen:
  
  I expect a 301 when I browse to http://localhost:8080/Plone/a/@@getVocabulary for both the values of the Accept header
  
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `Products/CMFPlone/browser/exceptions.py`
  Content:
  ```
  1 # -*- coding: utf-8 -*-
  2 from AccessControl import getSecurityManager
  3 from Products.Five import BrowserView
  4 from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
  5 from zExceptions.ExceptionFormatter import format_exception
  6 
  7 import json
  8 import sys
  9 
  10 
  11 class ExceptionView(BrowserView):
  12     basic_template = ViewPageTemplateFile('templates/basic_error_message.pt')
  13 
  14     def is_manager(self):
  15         return getSecurityManager().checkPermission(
  16             'Manage portal', self.context)
  17 
  18     def __call__(self):
  19         exception = self.context
  20         self.context = self.__parent__
  21         request = self.request
  22 
  23         error_type = exception.__class__.__name__
  24         exc_type, value, traceback = sys.exc_info()
  25         error_tb = ''.join(
  26             format_exception(exc_type, value, traceback, as_html=False))
  27         request.response.setStatus(exc_type)
  28 
  29         # Indicate exception as JSON
  30         if "text/html" not in request.getHeader('Accept', ''):
  31             request.response.setHeader("Content-Type", "application/json")
  32             return json.dumps({
  33                 'error_type': error_type,
  34             })
  35 
  36         # Render page with user-facing error notice
  37         request.set('disable_border', True)
  38         request.set('disable_plone.leftcolumn', True)
  39         request.set('disable_plone.rightcolumn', True)
  40 
  41         try:
  42             return self.index(
  43                 error_type=error_type,
  44                 error_tb=error_tb)
  45         except:
  46             return self.basic_template(
  47                 error_type=error_type,
  48                 error_tb=error_tb)
  49
  ```
  --- 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/Products/CMFPlone/browser/exceptions.py b/Products/CMFPlone/browser/exceptions.py
  --- a/Products/CMFPlone/browser/exceptions.py
  +++ b/Products/CMFPlone/browser/exceptions.py
  @@ -1,8 +1,10 @@
   # -*- coding: utf-8 -*-
   from AccessControl import getSecurityManager
  +from plone.memoize.view import memoize
   from Products.Five import BrowserView
   from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
   from zExceptions.ExceptionFormatter import format_exception
  +from zope.component import getMultiAdapter
   
   import json
   import sys
  @@ -15,12 +17,24 @@
           return getSecurityManager().checkPermission(
               'Manage portal', self.context)
   
  +    @property
  +    @memoize
  +    def plone_redirector_view(self):
  +        return getMultiAdapter(
  +            (self.__parent__, self.request), name="plone_redirector_view"
  +        )
  +
       def __call__(self):
           exception = self.context
  +        error_type = exception.__class__.__name__
  +        if error_type == "NotFound" and self.plone_redirector_view.attempt_redirect():
  +            # if a redirect is possible attempt_redirect returns True
  +            # and sets the proper location header
  +            return
  +
           self.context = self.__parent__
           request = self.request
   
  -        error_type = exception.__class__.__name__
           exc_type, value, traceback = sys.exc_info()
           error_tb = ''.join(
               format_exception(exc_type, value, traceback, as_html=False))
  
verification_info: {"golden_diff": "diff --git a/Products/CMFPlone/browser/exceptions.py b/Products/CMFPlone/browser/exceptions.py\n--- a/Products/CMFPlone/browser/exceptions.py\n+++ b/Products/CMFPlone/browser/exceptions.py\n@@ -1,8 +1,10 @@\n # -*- coding: utf-8 -*-\n from AccessControl import getSecurityManager\n+from plone.memoize.view import memoize\n from Products.Five import BrowserView\n from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile\n from zExceptions.ExceptionFormatter import format_exception\n+from zope.component import getMultiAdapter\n \n import json\n import sys\n@@ -15,12 +17,24 @@\n         return getSecurityManager().checkPermission(\n             'Manage portal', self.context)\n \n+    @property\n+    @memoize\n+    def plone_redirector_view(self):\n+        return getMultiAdapter(\n+            (self.__parent__, self.request), name=\"plone_redirector_view\"\n+        )\n+\n     def __call__(self):\n         exception = self.context\n+        error_type = exception.__class__.__name__\n+        if error_type == \"NotFound\" and self.plone_redirector_view.attempt_redirect():\n+            # if a redirect is possible attempt_redirect returns True\n+            # and sets the proper location header\n+            return\n+\n         self.context = self.__parent__\n         request = self.request\n \n-        error_type = exception.__class__.__name__\n         exc_type, value, traceback = sys.exc_info()\n         error_tb = ''.join(\n             format_exception(exc_type, value, traceback, as_html=False))\n", "issue": "Plone does not redirect ajax calls\n## BUG/PROBLEM REPORT (OR OTHER COMMON ISSUE)\r\n\r\n### What I did:\r\n\r\n1. Create a document with id \"a\"\r\n2. Rename it to \"b\"\r\n3. Run `curl -sI 'http://localhost:8080/Plone/a/@@getVocabulary'  -H 'Authorization: Basic YOURB64'  -H 'Accept: text/html'`: you will get a redirect (301) + some not useful text ignored by the browser\r\n4. Run `curl -sI 'http://localhost:8080/Plone/a/@@getVocabulary'  -H 'Authorization: Basic YOURB64'  -H 'Accept: application/json'` you will get a not found json text (status 404)\r\n\r\nNote that the two commands differ only for the `Accept` header.\r\n\r\n### What I expect to happen:\r\n\r\nI expect a 301 when I browse to http://localhost:8080/Plone/a/@@getVocabulary for both the values of the Accept header\r\n\n", "before_files": [{"content": "# -*- coding: utf-8 -*-\nfrom AccessControl import getSecurityManager\nfrom Products.Five import BrowserView\nfrom Products.Five.browser.pagetemplatefile import ViewPageTemplateFile\nfrom zExceptions.ExceptionFormatter import format_exception\n\nimport json\nimport sys\n\n\nclass ExceptionView(BrowserView):\n    basic_template = ViewPageTemplateFile('templates/basic_error_message.pt')\n\n    def is_manager(self):\n        return getSecurityManager().checkPermission(\n            'Manage portal', self.context)\n\n    def __call__(self):\n        exception = self.context\n        self.context = self.__parent__\n        request = self.request\n\n        error_type = exception.__class__.__name__\n        exc_type, value, traceback = sys.exc_info()\n        error_tb = ''.join(\n            format_exception(exc_type, value, traceback, as_html=False))\n        request.response.setStatus(exc_type)\n\n        # Indicate exception as JSON\n        if \"text/html\" not in request.getHeader('Accept', ''):\n            request.response.setHeader(\"Content-Type\", \"application/json\")\n            return json.dumps({\n                'error_type': error_type,\n            })\n\n        # Render page with user-facing error notice\n        request.set('disable_border', True)\n        request.set('disable_plone.leftcolumn', True)\n        request.set('disable_plone.rightcolumn', True)\n\n        try:\n            return self.index(\n                error_type=error_type,\n                error_tb=error_tb)\n        except:\n            return self.basic_template(\n                error_type=error_type,\n                error_tb=error_tb)\n", "path": "Products/CMFPlone/browser/exceptions.py"}], "after_files": [{"content": "# -*- coding: utf-8 -*-\nfrom AccessControl import getSecurityManager\nfrom plone.memoize.view import memoize\nfrom Products.Five import BrowserView\nfrom Products.Five.browser.pagetemplatefile import ViewPageTemplateFile\nfrom zExceptions.ExceptionFormatter import format_exception\nfrom zope.component import getMultiAdapter\n\nimport json\nimport sys\n\n\nclass ExceptionView(BrowserView):\n    basic_template = ViewPageTemplateFile('templates/basic_error_message.pt')\n\n    def is_manager(self):\n        return getSecurityManager().checkPermission(\n            'Manage portal', self.context)\n\n    @property\n    @memoize\n    def plone_redirector_view(self):\n        return getMultiAdapter(\n            (self.__parent__, self.request), name=\"plone_redirector_view\"\n        )\n\n    def __call__(self):\n        exception = self.context\n        error_type = exception.__class__.__name__\n        if error_type == \"NotFound\" and self.plone_redirector_view.attempt_redirect():\n            # if a redirect is possible attempt_redirect returns True\n            # and sets the proper location header\n            return\n\n        self.context = self.__parent__\n        request = self.request\n\n        exc_type, value, traceback = sys.exc_info()\n        error_tb = ''.join(\n            format_exception(exc_type, value, traceback, as_html=False))\n        request.response.setStatus(exc_type)\n\n        # Indicate exception as JSON\n        if \"text/html\" not in request.getHeader('Accept', ''):\n            request.response.setHeader(\"Content-Type\", \"application/json\")\n            return json.dumps({\n                'error_type': error_type,\n            })\n\n        # Render page with user-facing error notice\n        request.set('disable_border', True)\n        request.set('disable_plone.leftcolumn', True)\n        request.set('disable_plone.rightcolumn', True)\n\n        try:\n            return self.index(\n                error_type=error_type,\n                error_tb=error_tb)\n        except:\n            return self.basic_template(\n                error_type=error_type,\n                error_tb=error_tb)\n", "path": "Products/CMFPlone/browser/exceptions.py"}]}