prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Redirect full path to default version
  I'd like to be able to create versionless links to the documentation, for use in error messages, code comments, etc. For example, a message like `see https://click.palletsprojects.com/windows for more information`. I don't want to use URLs with versions because I would have to remember to modify all instances of it before releasing a new version.
  
  Currently, only the root path redirects to the default version, other paths raise a 404. Instead, the path should be preserved and appended to the default version path on redirect.
  
  ```
  Works:
  https://click.palletsprojects.com/ -> https://click.palletsprojects.com/en/7.x/
  
  Doesn't work, 404:
  https://click.palletsprojects.com/windows -> https://click.palletsprojects.com/en/7.x/windows
  ```
  
  I do not want to use the "latest" or "stable" versions because I would like the URLs that people land on and share to contain the actual version.
  
  I already do this with the transitional redirects I set up from `click.pocoo.org` to `click.palletsprojects.com`. A similar approach could probably be used to extend RTD's default redirect.
  
  ```nginx
  location ~ ^/dev(.*)$ {
    return 301 https://click.palletsprojects.com/en/master$1;
  }
  
  location ~ ^/(\d)(.*)$ {
    return 301 https://click.palletsprojects.com/en/$1.x$2;
  }
  
  location ~ ^/latest(.*)$ {
    return 301 https://click.palletsprojects.com/en/7.x$1;
  }
  
  location / {
    return 301 https://click.palletsprojects.com/en/7.x$request_uri;
  }
  ```
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `readthedocs/core/views/__init__.py`
  Content:
  ```
  1 # -*- coding: utf-8 -*-
  2 
  3 """
  4 Core views, including the main homepage,
  5 
  6 documentation and header rendering, and server errors.
  7 """
  8 
  9 from __future__ import absolute_import
  10 from __future__ import division
  11 import os
  12 import logging
  13 
  14 from django.conf import settings
  15 from django.http import HttpResponseRedirect, Http404, JsonResponse
  16 from django.shortcuts import render, get_object_or_404, redirect
  17 from django.views.decorators.csrf import csrf_exempt
  18 from django.views.generic import TemplateView
  19 
  20 from readthedocs.builds.models import Version
  21 from readthedocs.core.utils import broadcast
  22 from readthedocs.projects.models import Project, ImportedFile
  23 from readthedocs.projects.tasks import remove_dir
  24 from readthedocs.redirects.utils import get_redirect_response
  25 
  26 log = logging.getLogger(__name__)
  27 
  28 
  29 class NoProjectException(Exception):
  30     pass
  31 
  32 
  33 class HomepageView(TemplateView):
  34 
  35     template_name = 'homepage.html'
  36 
  37     def get_context_data(self, **kwargs):
  38         """Add latest builds and featured projects."""
  39         context = super(HomepageView, self).get_context_data(**kwargs)
  40         context['featured_list'] = Project.objects.filter(featured=True)
  41         context['projects_count'] = Project.objects.count()
  42         return context
  43 
  44 
  45 class SupportView(TemplateView):
  46     template_name = 'support.html'
  47 
  48     def get_context_data(self, **kwargs):
  49         context = super(SupportView, self).get_context_data(**kwargs)
  50         support_email = getattr(settings, 'SUPPORT_EMAIL', None)
  51         if not support_email:
  52             support_email = 'support@{domain}'.format(
  53                 domain=getattr(
  54                     settings,
  55                     'PRODUCTION_DOMAIN',
  56                     'readthedocs.org',
  57                 ),
  58             )
  59 
  60         context['support_email'] = support_email
  61         return context
  62 
  63 
  64 def random_page(request, project_slug=None):  # pylint: disable=unused-argument
  65     imported_file = ImportedFile.objects.order_by('?')
  66     if project_slug:
  67         imported_file = imported_file.filter(project__slug=project_slug)
  68     imported_file = imported_file.first()
  69     if imported_file is None:
  70         raise Http404
  71     url = imported_file.get_absolute_url()
  72     return HttpResponseRedirect(url)
  73 
  74 
  75 @csrf_exempt
  76 def wipe_version(request, project_slug, version_slug):
  77     version = get_object_or_404(
  78         Version,
  79         project__slug=project_slug,
  80         slug=version_slug,
  81     )
  82     # We need to check by ``for_admin_user`` here to allow members of the
  83     # ``Admin`` team (which doesn't own the project) under the corporate site.
  84     if version.project not in Project.objects.for_admin_user(user=request.user):
  85         raise Http404('You must own this project to wipe it.')
  86 
  87     if request.method == 'POST':
  88         del_dirs = [
  89             os.path.join(version.project.doc_path, 'checkouts', version.slug),
  90             os.path.join(version.project.doc_path, 'envs', version.slug),
  91             os.path.join(version.project.doc_path, 'conda', version.slug),
  92         ]
  93         for del_dir in del_dirs:
  94             broadcast(type='build', task=remove_dir, args=[del_dir])
  95         return redirect('project_version_list', project_slug)
  96     return render(
  97         request,
  98         'wipe_version.html',
  99         {'version': version, 'project': version.project},
  100     )
  101 
  102 
  103 def server_error_500(request, template_name='500.html'):
  104     """A simple 500 handler so we get media."""
  105     r = render(request, template_name)
  106     r.status_code = 500
  107     return r
  108 
  109 
  110 def server_error_404(request, exception=None, template_name='404.html'):  # pylint: disable=unused-argument  # noqa
  111     """
  112     A simple 404 handler so we get media.
  113 
  114     .. note::
  115 
  116         Marking exception as optional to make /404/ testing page to work.
  117     """
  118     response = get_redirect_response(request, path=request.get_full_path())
  119     if response:
  120         return response
  121     r = render(request, template_name)
  122     r.status_code = 404
  123     return r
  124 
  125 
  126 def do_not_track(request):
  127     dnt_header = request.META.get('HTTP_DNT')
  128 
  129     # https://w3c.github.io/dnt/drafts/tracking-dnt.html#status-representation
  130     return JsonResponse({   # pylint: disable=redundant-content-type-for-json-response
  131         'policy': 'https://docs.readthedocs.io/en/latest/privacy-policy.html',
  132         'same-party': [
  133             'readthedocs.org',
  134             'readthedocs.com',
  135             'readthedocs.io',           # .org Documentation Sites
  136             'readthedocs-hosted.com',   # .com Documentation Sites
  137         ],
  138         'tracking': 'N' if dnt_header == '1' else 'T',
  139     }, content_type='application/tracking-status+json')
  140
  ```
  --- 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/readthedocs/core/views/__init__.py b/readthedocs/core/views/__init__.py
  --- a/readthedocs/core/views/__init__.py
  +++ b/readthedocs/core/views/__init__.py
  @@ -116,8 +116,16 @@
           Marking exception as optional to make /404/ testing page to work.
       """
       response = get_redirect_response(request, path=request.get_full_path())
  +
       if response:
  -        return response
  +        if response.url == request.build_absolute_uri():
  +            # check that we do have a response and avoid infinite redirect
  +            log.warning(
  +                'Infinite Redirect: FROM URL is the same than TO URL. url=%s',
  +                response.url,
  +            )
  +        else:
  +            return response
       r = render(request, template_name)
       r.status_code = 404
       return r
  
verification_info: {"golden_diff": "diff --git a/readthedocs/core/views/__init__.py b/readthedocs/core/views/__init__.py\n--- a/readthedocs/core/views/__init__.py\n+++ b/readthedocs/core/views/__init__.py\n@@ -116,8 +116,16 @@\n         Marking exception as optional to make /404/ testing page to work.\n     \"\"\"\n     response = get_redirect_response(request, path=request.get_full_path())\n+\n     if response:\n-        return response\n+        if response.url == request.build_absolute_uri():\n+            # check that we do have a response and avoid infinite redirect\n+            log.warning(\n+                'Infinite Redirect: FROM URL is the same than TO URL. url=%s',\n+                response.url,\n+            )\n+        else:\n+            return response\n     r = render(request, template_name)\n     r.status_code = 404\n     return r\n", "issue": "Redirect full path to default version\nI'd like to be able to create versionless links to the documentation, for use in error messages, code comments, etc. For example, a message like `see https://click.palletsprojects.com/windows for more information`. I don't want to use URLs with versions because I would have to remember to modify all instances of it before releasing a new version.\r\n\r\nCurrently, only the root path redirects to the default version, other paths raise a 404. Instead, the path should be preserved and appended to the default version path on redirect.\r\n\r\n```\r\nWorks:\r\nhttps://click.palletsprojects.com/ -> https://click.palletsprojects.com/en/7.x/\r\n\r\nDoesn't work, 404:\r\nhttps://click.palletsprojects.com/windows -> https://click.palletsprojects.com/en/7.x/windows\r\n```\r\n\r\nI do not want to use the \"latest\" or \"stable\" versions because I would like the URLs that people land on and share to contain the actual version.\r\n\r\nI already do this with the transitional redirects I set up from `click.pocoo.org` to `click.palletsprojects.com`. A similar approach could probably be used to extend RTD's default redirect.\r\n\r\n```nginx\r\nlocation ~ ^/dev(.*)$ {\r\n  return 301 https://click.palletsprojects.com/en/master$1;\r\n}\r\n\r\nlocation ~ ^/(\\d)(.*)$ {\r\n  return 301 https://click.palletsprojects.com/en/$1.x$2;\r\n}\r\n\r\nlocation ~ ^/latest(.*)$ {\r\n  return 301 https://click.palletsprojects.com/en/7.x$1;\r\n}\r\n\r\nlocation / {\r\n  return 301 https://click.palletsprojects.com/en/7.x$request_uri;\r\n}\r\n```\n", "before_files": [{"content": "# -*- coding: utf-8 -*-\n\n\"\"\"\nCore views, including the main homepage,\n\ndocumentation and header rendering, and server errors.\n\"\"\"\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nimport os\nimport logging\n\nfrom django.conf import settings\nfrom django.http import HttpResponseRedirect, Http404, JsonResponse\nfrom django.shortcuts import render, get_object_or_404, redirect\nfrom django.views.decorators.csrf import csrf_exempt\nfrom django.views.generic import TemplateView\n\nfrom readthedocs.builds.models import Version\nfrom readthedocs.core.utils import broadcast\nfrom readthedocs.projects.models import Project, ImportedFile\nfrom readthedocs.projects.tasks import remove_dir\nfrom readthedocs.redirects.utils import get_redirect_response\n\nlog = logging.getLogger(__name__)\n\n\nclass NoProjectException(Exception):\n    pass\n\n\nclass HomepageView(TemplateView):\n\n    template_name = 'homepage.html'\n\n    def get_context_data(self, **kwargs):\n        \"\"\"Add latest builds and featured projects.\"\"\"\n        context = super(HomepageView, self).get_context_data(**kwargs)\n        context['featured_list'] = Project.objects.filter(featured=True)\n        context['projects_count'] = Project.objects.count()\n        return context\n\n\nclass SupportView(TemplateView):\n    template_name = 'support.html'\n\n    def get_context_data(self, **kwargs):\n        context = super(SupportView, self).get_context_data(**kwargs)\n        support_email = getattr(settings, 'SUPPORT_EMAIL', None)\n        if not support_email:\n            support_email = 'support@{domain}'.format(\n                domain=getattr(\n                    settings,\n                    'PRODUCTION_DOMAIN',\n                    'readthedocs.org',\n                ),\n            )\n\n        context['support_email'] = support_email\n        return context\n\n\ndef random_page(request, project_slug=None):  # pylint: disable=unused-argument\n    imported_file = ImportedFile.objects.order_by('?')\n    if project_slug:\n        imported_file = imported_file.filter(project__slug=project_slug)\n    imported_file = imported_file.first()\n    if imported_file is None:\n        raise Http404\n    url = imported_file.get_absolute_url()\n    return HttpResponseRedirect(url)\n\n\n@csrf_exempt\ndef wipe_version(request, project_slug, version_slug):\n    version = get_object_or_404(\n        Version,\n        project__slug=project_slug,\n        slug=version_slug,\n    )\n    # We need to check by ``for_admin_user`` here to allow members of the\n    # ``Admin`` team (which doesn't own the project) under the corporate site.\n    if version.project not in Project.objects.for_admin_user(user=request.user):\n        raise Http404('You must own this project to wipe it.')\n\n    if request.method == 'POST':\n        del_dirs = [\n            os.path.join(version.project.doc_path, 'checkouts', version.slug),\n            os.path.join(version.project.doc_path, 'envs', version.slug),\n            os.path.join(version.project.doc_path, 'conda', version.slug),\n        ]\n        for del_dir in del_dirs:\n            broadcast(type='build', task=remove_dir, args=[del_dir])\n        return redirect('project_version_list', project_slug)\n    return render(\n        request,\n        'wipe_version.html',\n        {'version': version, 'project': version.project},\n    )\n\n\ndef server_error_500(request, template_name='500.html'):\n    \"\"\"A simple 500 handler so we get media.\"\"\"\n    r = render(request, template_name)\n    r.status_code = 500\n    return r\n\n\ndef server_error_404(request, exception=None, template_name='404.html'):  # pylint: disable=unused-argument  # noqa\n    \"\"\"\n    A simple 404 handler so we get media.\n\n    .. note::\n\n        Marking exception as optional to make /404/ testing page to work.\n    \"\"\"\n    response = get_redirect_response(request, path=request.get_full_path())\n    if response:\n        return response\n    r = render(request, template_name)\n    r.status_code = 404\n    return r\n\n\ndef do_not_track(request):\n    dnt_header = request.META.get('HTTP_DNT')\n\n    # https://w3c.github.io/dnt/drafts/tracking-dnt.html#status-representation\n    return JsonResponse({   # pylint: disable=redundant-content-type-for-json-response\n        'policy': 'https://docs.readthedocs.io/en/latest/privacy-policy.html',\n        'same-party': [\n            'readthedocs.org',\n            'readthedocs.com',\n            'readthedocs.io',           # .org Documentation Sites\n            'readthedocs-hosted.com',   # .com Documentation Sites\n        ],\n        'tracking': 'N' if dnt_header == '1' else 'T',\n    }, content_type='application/tracking-status+json')\n", "path": "readthedocs/core/views/__init__.py"}], "after_files": [{"content": "# -*- coding: utf-8 -*-\n\n\"\"\"\nCore views, including the main homepage,\n\ndocumentation and header rendering, and server errors.\n\"\"\"\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nimport os\nimport logging\n\nfrom django.conf import settings\nfrom django.http import HttpResponseRedirect, Http404, JsonResponse\nfrom django.shortcuts import render, get_object_or_404, redirect\nfrom django.views.decorators.csrf import csrf_exempt\nfrom django.views.generic import TemplateView\n\nfrom readthedocs.builds.models import Version\nfrom readthedocs.core.utils import broadcast\nfrom readthedocs.projects.models import Project, ImportedFile\nfrom readthedocs.projects.tasks import remove_dir\nfrom readthedocs.redirects.utils import get_redirect_response\n\nlog = logging.getLogger(__name__)\n\n\nclass NoProjectException(Exception):\n    pass\n\n\nclass HomepageView(TemplateView):\n\n    template_name = 'homepage.html'\n\n    def get_context_data(self, **kwargs):\n        \"\"\"Add latest builds and featured projects.\"\"\"\n        context = super(HomepageView, self).get_context_data(**kwargs)\n        context['featured_list'] = Project.objects.filter(featured=True)\n        context['projects_count'] = Project.objects.count()\n        return context\n\n\nclass SupportView(TemplateView):\n    template_name = 'support.html'\n\n    def get_context_data(self, **kwargs):\n        context = super(SupportView, self).get_context_data(**kwargs)\n        support_email = getattr(settings, 'SUPPORT_EMAIL', None)\n        if not support_email:\n            support_email = 'support@{domain}'.format(\n                domain=getattr(\n                    settings,\n                    'PRODUCTION_DOMAIN',\n                    'readthedocs.org',\n                ),\n            )\n\n        context['support_email'] = support_email\n        return context\n\n\ndef random_page(request, project_slug=None):  # pylint: disable=unused-argument\n    imported_file = ImportedFile.objects.order_by('?')\n    if project_slug:\n        imported_file = imported_file.filter(project__slug=project_slug)\n    imported_file = imported_file.first()\n    if imported_file is None:\n        raise Http404\n    url = imported_file.get_absolute_url()\n    return HttpResponseRedirect(url)\n\n\n@csrf_exempt\ndef wipe_version(request, project_slug, version_slug):\n    version = get_object_or_404(\n        Version,\n        project__slug=project_slug,\n        slug=version_slug,\n    )\n    # We need to check by ``for_admin_user`` here to allow members of the\n    # ``Admin`` team (which doesn't own the project) under the corporate site.\n    if version.project not in Project.objects.for_admin_user(user=request.user):\n        raise Http404('You must own this project to wipe it.')\n\n    if request.method == 'POST':\n        del_dirs = [\n            os.path.join(version.project.doc_path, 'checkouts', version.slug),\n            os.path.join(version.project.doc_path, 'envs', version.slug),\n            os.path.join(version.project.doc_path, 'conda', version.slug),\n        ]\n        for del_dir in del_dirs:\n            broadcast(type='build', task=remove_dir, args=[del_dir])\n        return redirect('project_version_list', project_slug)\n    return render(\n        request,\n        'wipe_version.html',\n        {'version': version, 'project': version.project},\n    )\n\n\ndef server_error_500(request, template_name='500.html'):\n    \"\"\"A simple 500 handler so we get media.\"\"\"\n    r = render(request, template_name)\n    r.status_code = 500\n    return r\n\n\ndef server_error_404(request, exception=None, template_name='404.html'):  # pylint: disable=unused-argument  # noqa\n    \"\"\"\n    A simple 404 handler so we get media.\n\n    .. note::\n\n        Marking exception as optional to make /404/ testing page to work.\n    \"\"\"\n    response = get_redirect_response(request, path=request.get_full_path())\n\n    if response:\n        if response.url == request.build_absolute_uri():\n            # check that we do have a response and avoid infinite redirect\n            log.warning(\n                'Infinite Redirect: FROM URL is the same than TO URL. url=%s',\n                response.url,\n            )\n        else:\n            return response\n    r = render(request, template_name)\n    r.status_code = 404\n    return r\n\n\ndef do_not_track(request):\n    dnt_header = request.META.get('HTTP_DNT')\n\n    # https://w3c.github.io/dnt/drafts/tracking-dnt.html#status-representation\n    return JsonResponse({   # pylint: disable=redundant-content-type-for-json-response\n        'policy': 'https://docs.readthedocs.io/en/latest/privacy-policy.html',\n        'same-party': [\n            'readthedocs.org',\n            'readthedocs.com',\n            'readthedocs.io',           # .org Documentation Sites\n            'readthedocs-hosted.com',   # .com Documentation Sites\n        ],\n        'tracking': 'N' if dnt_header == '1' else 'T',\n    }, content_type='application/tracking-status+json')\n", "path": "readthedocs/core/views/__init__.py"}]}