diff --git a/readthedocs/core/views/__init__.py b/readthedocs/core/views/__init__.py
index cc05c6541df..091c056cc26 100644
--- a/readthedocs/core/views/__init__.py
+++ b/readthedocs/core/views/__init__.py
@@ -93,6 +93,8 @@ class ErrorView(TemplateView):
multiple subpaths for errors, as we need to show application themed errors
for dashboard users and minimal error pages for documentation readers.
+ Template resolution also uses fallback to generic 4xx/5xx error templates.
+
View arguments:
status_code
@@ -105,33 +107,31 @@ class ErrorView(TemplateView):
separate path from Proxito error templates.
"""
- base_path = "errors/dashboard/"
- status_code = 500
+ base_path = "errors/dashboard"
+ status_code = None
+ template_name = None
def get_status_code(self):
- status_code = self.status_code
- try:
- status_code = int(self.kwargs["status_code"])
- except (ValueError, KeyError):
- pass
- return status_code
+ return self.kwargs.get("status_code", self.status_code)
+
+ def get_template_name(self):
+ return self.kwargs.get("template_name", self.template_name)
def get_template_names(self):
- status_code = self.get_status_code()
- if settings.RTD_EXT_THEME_ENABLED:
- # First try to load the template for the specific HTTP status code
- # and fall back to a generic 400/500 level error template
- status_code_class = int(status_code / 100)
- generic_code = f"{status_code_class}xx"
- return [
- f"{self.base_path}/{code}.html" for code in [status_code, generic_code]
- ]
- # TODO the legacy dashboard has top level path errors, as is the
- # default. This can be removed later.
- return f"{status_code}.html"
+ template_names = []
+ if (template_name := self.get_template_name()) is not None:
+ template_names.append(template_name.rstrip("/"))
+ if (status_code := self.get_status_code()) is not None:
+ template_names.append(str(status_code))
+ return [f"{self.base_path}/{file}.html" for file in template_names]
+
+ def get_context_data(self, **kwargs):
+ context_data = super().get_context_data(**kwargs)
+ context_data["status_code"] = self.get_status_code()
+ return context_data
def dispatch(self, request, *args, **kwargs):
- context = self.get_context_data(**kwargs)
+ context = self.get_context_data()
status_code = self.get_status_code()
return self.render_to_response(
context,
@@ -139,15 +139,6 @@ def dispatch(self, request, *args, **kwargs):
)
-# TODO replace this with ErrorView and a template in `errors/` instead
-class TeapotView(TemplateView):
- template_name = "core/teapot.html"
-
- def get(self, request, *args, **kwargs):
- context = self.get_context_data(**kwargs)
- return self.render_to_response(context, status=418)
-
-
class PageNotFoundView(View):
"""Just a 404 view that ignores all URL parameters."""
diff --git a/readthedocs/projects/views/base.py b/readthedocs/projects/views/base.py
index 6ddd6e9fd75..f0a092cc08c 100644
--- a/readthedocs/projects/views/base.py
+++ b/readthedocs/projects/views/base.py
@@ -107,9 +107,7 @@ def get(self, request, *args, **kwargs):
)
if is_show_dashboard_denied(self.get_project()):
- template_name = "spam.html"
- if settings.RTD_EXT_THEME_ENABLED:
- template_name = "errors/dashboard/410.html"
+ template_name = "errors/dashboard/spam.html"
return render(request, template_name=template_name, status=410)
return super().get(request, *args, **kwargs)
diff --git a/readthedocs/proxito/exceptions.py b/readthedocs/proxito/exceptions.py
index 813037be633..6446c567bc9 100644
--- a/readthedocs/proxito/exceptions.py
+++ b/readthedocs/proxito/exceptions.py
@@ -19,7 +19,7 @@ class ContextualizedHttp404(Http404):
The contextualized exception is handled by proxito's 404 handler
"""
- template_name = "errors/404/base.html"
+ template_name = "errors/proxito/404/base.html"
not_found_subject = pgettext_lazy(_not_found_subject_translation_context, "page")
def __init__(self, http_status=404, path_not_found=None, **kwargs):
@@ -48,10 +48,8 @@ class DomainDNSHttp404(ContextualizedHttp404):
"""Raised if a DNS record points to us and we don't know the domain."""
- template_name = "errors/404/dns.html"
- not_found_subject = pgettext_lazy(
- _not_found_subject_translation_context, "matching DNS record"
- )
+ template_name = "errors/proxito/404/dns.html"
+ not_found_subject = pgettext_lazy(_not_found_subject_translation_context, "domain")
def __init__(self, domain, **kwargs):
"""
@@ -73,7 +71,7 @@ class ProjectHttp404(ContextualizedHttp404):
It indicates a number of reasons for the user.
"""
- template_name = "errors/404/no_project.html"
+ template_name = "errors/proxito/404/no_project.html"
not_found_subject = pgettext_lazy(_not_found_subject_translation_context, "project")
def __init__(self, domain, **kwargs):
@@ -91,7 +89,7 @@ class SubprojectHttp404(ContextualizedHttp404):
"""Raised if a subproject was not found."""
- template_name = "errors/404/no_subproject.html"
+ template_name = "errors/proxito/404/no_subproject.html"
not_found_subject = pgettext_lazy(
"Names an object not found in a 404 error", "subproject"
)
@@ -111,7 +109,7 @@ class ProjectFilenameHttp404(ContextualizedHttp404):
"""Raised if a page inside an existing project was not found."""
- template_name = "errors/404/no_project_page.html"
+ template_name = "errors/proxito/404/no_project_page.html"
not_found_subject = pgettext_lazy(
_not_found_subject_translation_context, "documentation page"
)
@@ -136,7 +134,7 @@ class ProjectTranslationHttp404(ContextualizedHttp404):
If a page isn't found, raise a ProjectPageHttp404.
"""
- template_name = "errors/404/no_language.html"
+ template_name = "errors/proxito/404/no_language.html"
not_found_subject = pgettext_lazy(
"Names an object not found in a 404 error", "translation"
)
@@ -160,7 +158,7 @@ class ProjectVersionHttp404(ContextualizedHttp404):
Note: The containing project can be a subproject.
"""
- template_name = "errors/404/no_version.html"
+ template_name = "errors/proxito/404/no_version.html"
not_found_subject = pgettext_lazy(
_not_found_subject_translation_context, "documentation version"
)
diff --git a/readthedocs/proxito/urls.py b/readthedocs/proxito/urls.py
index c28cee7edea..b5093015183 100644
--- a/readthedocs/proxito/urls.py
+++ b/readthedocs/proxito/urls.py
@@ -33,12 +33,14 @@
pip.rtd.io/_/api/*
"""
+from functools import reduce
+from operator import add
+
from django.conf import settings
from django.urls import include, path, re_path
-from django.views import defaults
from readthedocs.constants import pattern_opts
-from readthedocs.core.views import HealthCheckView, TeapotView
+from readthedocs.core.views import HealthCheckView
from readthedocs.projects.views.public import ProjectDownloadMedia
from readthedocs.proxito.views.hosting import ReadTheDocsConfigJson
from readthedocs.proxito.views.serve import (
@@ -49,7 +51,7 @@
ServeSitemapXML,
ServeStaticFiles,
)
-from readthedocs.proxito.views.utils import proxito_404_page_handler
+from readthedocs.proxito.views.utils import ProxitoErrorView, proxito_404_page_handler
DOC_PATH_PREFIX = getattr(settings, "DOC_PATH_PREFIX", "")
@@ -154,19 +156,19 @@
# /projects//
re_path(
r"^projects/(?P{project_slug})/$".format(**pattern_opts),
- TeapotView.as_view(),
+ ProxitoErrorView.as_view(status_code=418),
name="projects_detail",
),
# /projects//builds/
re_path(
(r"^projects/(?P{project_slug})/builds/$".format(**pattern_opts)),
- TeapotView.as_view(),
+ ProxitoErrorView.as_view(status_code=418),
name="builds_project_list",
),
# /projects//versions/
re_path(
r"^projects/(?P{project_slug})/versions/$".format(**pattern_opts),
- TeapotView.as_view(),
+ ProxitoErrorView.as_view(status_code=418),
name="project_version_list",
),
# /projects//downloads/
@@ -176,7 +178,7 @@
**pattern_opts
)
),
- TeapotView.as_view(),
+ ProxitoErrorView.as_view(status_code=418),
name="project_downloads",
),
# /projects//builds//
@@ -186,21 +188,41 @@
**pattern_opts
)
),
- TeapotView.as_view(),
+ ProxitoErrorView.as_view(status_code=418),
name="builds_detail",
),
# /projects//version//
re_path(
r"^projects/(?P[-\w]+)/version/(?P[^/]+)/edit/$",
- TeapotView.as_view(),
+ ProxitoErrorView.as_view(status_code=418),
name="project_version_detail",
),
]
-urlpatterns = (
- health_check_urls + proxied_urls + core_urls + docs_urls + dummy_dashboard_urls
-)
+debug_urls = [
+ # For testing error responses and templates
+ re_path(
+ r"^{DOC_PATH_PREFIX}error/(?P.*)$".format(
+ DOC_PATH_PREFIX=DOC_PATH_PREFIX,
+ ),
+ ProxitoErrorView.as_view(),
+ ),
+]
+
+groups = [
+ health_check_urls,
+ proxied_urls,
+ core_urls,
+ docs_urls,
+ # Fallback paths only required for resolving URLs, evaluate these last
+ dummy_dashboard_urls,
+]
+
+if settings.SHOW_DEBUG_TOOLBAR:
+ groups.insert(0, debug_urls)
+
+urlpatterns = reduce(add, groups)
# Use Django default error handlers to make things simpler
handler404 = proxito_404_page_handler
-handler500 = defaults.server_error
+handler500 = ProxitoErrorView.as_view(status_code=500)
diff --git a/readthedocs/proxito/views/mixins.py b/readthedocs/proxito/views/mixins.py
index 0b014115275..600556936c0 100644
--- a/readthedocs/proxito/views/mixins.py
+++ b/readthedocs/proxito/views/mixins.py
@@ -272,7 +272,9 @@ def _spam_response(self, request, project):
from readthedocsext.spamfighting.utils import is_serve_docs_denied # noqa
if is_serve_docs_denied(project):
- return render(request, template_name="spam.html", status=410)
+ return render(
+ request, template_name="errors/proxito/spam.html", status=410
+ )
class ServeRedirectMixin:
diff --git a/readthedocs/proxito/views/utils.py b/readthedocs/proxito/views/utils.py
index c33a76a3833..527e3341377 100644
--- a/readthedocs/proxito/views/utils.py
+++ b/readthedocs/proxito/views/utils.py
@@ -2,11 +2,17 @@
from django.http import HttpResponse
from django.shortcuts import render
+from readthedocs.core.views import ErrorView
+
from ..exceptions import ContextualizedHttp404
log = structlog.get_logger(__name__) # noqa
+class ProxitoErrorView(ErrorView):
+ base_path = "errors/proxito"
+
+
def fast_404(request, *args, **kwargs):
"""
A fast error page handler.
@@ -18,7 +24,7 @@ def fast_404(request, *args, **kwargs):
def proxito_404_page_handler(
- request, template_name="errors/404/base.html", exception=None
+ request, template_name="errors/proxito/404/base.html", exception=None
):
"""
Serves a 404 error message, handling 404 exception types raised throughout the app.
diff --git a/readthedocs/templates/404.html b/readthedocs/templates/404.html
deleted file mode 100644
index 81bb1261bc4..00000000000
--- a/readthedocs/templates/404.html
+++ /dev/null
@@ -1,2 +0,0 @@
-{% extends "errors/404/base.html" %}
-{# this is the default template, used by Django's default 404 handler. Proxito has its own 404 view, but the main application doesn't (yet) #}
diff --git a/readthedocs/templates/errors/404/no_language.html b/readthedocs/templates/errors/404/no_language.html
deleted file mode 100644
index 015a9397d5d..00000000000
--- a/readthedocs/templates/errors/404/no_language.html
+++ /dev/null
@@ -1,10 +0,0 @@
-{% extends "errors/404/base.html" %}
-{% load i18n %}
-
-{# project exists but not in the requested language #}
-
-{% block 404_error_message %}
- {% include "errors/404/include_error_message.html" %}
- {% include "errors/404/include_search.html" with search_project=project.slug %}
- {% include "errors/404/include_tips.html" %}
-{% endblock %}
diff --git a/readthedocs/templates/errors/404/no_project_page.html b/readthedocs/templates/errors/404/no_project_page.html
deleted file mode 100644
index 4b10444fd8d..00000000000
--- a/readthedocs/templates/errors/404/no_project_page.html
+++ /dev/null
@@ -1,10 +0,0 @@
-{% extends "errors/404/base.html" %}
-{% load i18n %}
-
-{# project exists but page doesn't #}
-
-{% block 404_error_message %}
- {% include "errors/404/include_error_message.html" %}
- {% include "errors/404/include_search.html" with search_project=project.slug %}
- {% include "errors/404/include_tips.html" %}
-{% endblock %}
diff --git a/readthedocs/templates/errors/404/no_subproject.html b/readthedocs/templates/errors/404/no_subproject.html
deleted file mode 100644
index 6958f403e13..00000000000
--- a/readthedocs/templates/errors/404/no_subproject.html
+++ /dev/null
@@ -1,10 +0,0 @@
-{% extends "errors/404/base.html" %}
-{% load i18n %}
-
-{# project exists but subproject doesn't #}
-
-{% block 404_error_message %}
- {% include "errors/404/include_error_message.html" %}
- {% include "errors/404/include_search.html" with search_project=project.slug %}
- {% include "errors/404/include_tips.html" %}
-{% endblock %}
diff --git a/readthedocs/templates/errors/404/no_version.html b/readthedocs/templates/errors/404/no_version.html
deleted file mode 100644
index 3e4d62c939d..00000000000
--- a/readthedocs/templates/errors/404/no_version.html
+++ /dev/null
@@ -1,10 +0,0 @@
-{% extends "errors/404/base.html" %}
-{% load i18n %}
-
-{# project exists but version doesn't #}
-
-{% block 404_error_message %}
- {% include "errors/404/include_error_message.html" %}
- {% include "errors/404/include_search.html" with search_project=project.slug %}
- {% include "errors/404/include_tips.html" %}
-{% endblock %}
diff --git a/readthedocs/templates/401.html b/readthedocs/templates/errors/dashboard/401.html
similarity index 87%
rename from readthedocs/templates/401.html
rename to readthedocs/templates/errors/dashboard/401.html
index b5db786a6df..c50f8a26c6c 100644
--- a/readthedocs/templates/401.html
+++ b/readthedocs/templates/errors/dashboard/401.html
@@ -1,4 +1,4 @@
-{% extends "errors/base.html" %}
+{% extends "errors/dashboard/base.html" %}
{% load core_tags %}
{% load i18n %}
diff --git a/readthedocs/templates/403.html b/readthedocs/templates/errors/dashboard/403.html
similarity index 100%
rename from readthedocs/templates/403.html
rename to readthedocs/templates/errors/dashboard/403.html
diff --git a/readthedocs/templates/errors/dashboard/404.html b/readthedocs/templates/errors/dashboard/404.html
new file mode 100644
index 00000000000..7b83c17302a
--- /dev/null
+++ b/readthedocs/templates/errors/dashboard/404.html
@@ -0,0 +1,46 @@
+{% extends "errors/dashboard/base.html" %}
+{% load core_tags %}
+{% load i18n %}
+
+{% block title %}
+ {% trans "404 Not Found" %}
+{% endblock %}
+
+{% block content %}
+
+{% trans "404 Not Found" %}
+
+ {% block 404_error_message %}
+
+ {% trans "You have encountered a 404 on Read the Docs. You are either looking for a page that does not exist or a project that has been removed." %}
+
+
+
+
+ \ What a maze! /
+ \ /
+ \ This page does /
+ ] not exist. [ ,'|
+ ] [ / |
+ ]___ ___[ ,' |
+ ] ]\ /[ [ |: |
+ ] ] \ / [ [ |: |
+ ] ] ] [ [ [ |: |
+ ] ] ]__ __[ [ [ |: |
+ ] ] ] ]\ _ /[ [ [ [ |: |
+ ] ] ] ] (#) [ [ [ [ :===='
+ ] ] ]_].nHn.[_[ [ [
+ ] ] ] HHHHH. [ [ [
+ ] ] / `HH("N \ [ [
+ ]__]/ HHH " \[__[
+ ] NNN [
+ ] N/" [
+ ] N H [
+ / N \
+ / q, \
+ / \
+
+
+ {% endblock %}
+
+{% endblock %}
diff --git a/readthedocs/templates/429.html b/readthedocs/templates/errors/dashboard/429.html
similarity index 89%
rename from readthedocs/templates/429.html
rename to readthedocs/templates/errors/dashboard/429.html
index 600a2b3ff7a..aa7de6f707f 100644
--- a/readthedocs/templates/429.html
+++ b/readthedocs/templates/errors/dashboard/429.html
@@ -1,4 +1,4 @@
-{% extends "errors/base.html" %}
+{% extends "errors/dashboard/base.html" %}
{% load core_tags %}
{% load i18n %}
diff --git a/readthedocs/templates/errors/dashboard/4xx.html b/readthedocs/templates/errors/dashboard/4xx.html
new file mode 100644
index 00000000000..ea595681c2e
--- /dev/null
+++ b/readthedocs/templates/errors/dashboard/4xx.html
@@ -0,0 +1,20 @@
+{% extends "errors/dashboard/base.html" %}
+{% load core_tags %}
+{% load i18n %}
+
+{% block title %}
+ {% trans "Bad request" %}
+{% endblock %}
+
+{% block content %}
+
+ {% blocktrans trimmed %}
+ Bad request
+ {% endblocktrans %}
+
+
+ {% blocktrans trimmed %}
+ Your request is either unsupported or we didn't understand it.
+ {% endblocktrans %}
+
+{% endblock %}
diff --git a/readthedocs/templates/500.html b/readthedocs/templates/errors/dashboard/500.html
similarity index 88%
rename from readthedocs/templates/500.html
rename to readthedocs/templates/errors/dashboard/500.html
index 381ca625572..61e68c00247 100644
--- a/readthedocs/templates/500.html
+++ b/readthedocs/templates/errors/dashboard/500.html
@@ -1,4 +1,4 @@
-{% extends "errors/base.html" %}
+{% extends "errors/dashboard/base.html" %}
{% load i18n %}
{% block title %}
diff --git a/readthedocs/templates/errors/dashboard/5xx.html b/readthedocs/templates/errors/dashboard/5xx.html
new file mode 120000
index 00000000000..0f3fa109755
--- /dev/null
+++ b/readthedocs/templates/errors/dashboard/5xx.html
@@ -0,0 +1 @@
+500.html
\ No newline at end of file
diff --git a/readthedocs/templates/errors/dashboard/base.html b/readthedocs/templates/errors/dashboard/base.html
new file mode 100644
index 00000000000..d6dd60f4cfa
--- /dev/null
+++ b/readthedocs/templates/errors/dashboard/base.html
@@ -0,0 +1 @@
+{% extends "errors/base.html" %}
diff --git a/readthedocs/templates/spam.html b/readthedocs/templates/errors/dashboard/spam.html
similarity index 100%
rename from readthedocs/templates/spam.html
rename to readthedocs/templates/errors/dashboard/spam.html
diff --git a/readthedocs/templates/errors/proxito/401.html b/readthedocs/templates/errors/proxito/401.html
index c1da9224c41..f479f7f09fb 120000
--- a/readthedocs/templates/errors/proxito/401.html
+++ b/readthedocs/templates/errors/proxito/401.html
@@ -1 +1 @@
-../../401.html
\ No newline at end of file
+../dashboard/401.html
\ No newline at end of file
diff --git a/readthedocs/templates/errors/proxito/403.html b/readthedocs/templates/errors/proxito/403.html
new file mode 120000
index 00000000000..f479f7f09fb
--- /dev/null
+++ b/readthedocs/templates/errors/proxito/403.html
@@ -0,0 +1 @@
+../dashboard/401.html
\ No newline at end of file
diff --git a/readthedocs/templates/errors/404/base.html b/readthedocs/templates/errors/proxito/404/base.html
similarity index 96%
rename from readthedocs/templates/errors/404/base.html
rename to readthedocs/templates/errors/proxito/404/base.html
index 524fde40346..57401598fb1 100644
--- a/readthedocs/templates/errors/404/base.html
+++ b/readthedocs/templates/errors/proxito/404/base.html
@@ -1,4 +1,4 @@
-{% extends "errors/base.html" %}
+{% extends "errors/proxito/base.html" %}
{% load core_tags %}
{% load i18n %}
diff --git a/readthedocs/templates/errors/404/dns.html b/readthedocs/templates/errors/proxito/404/dns.html
similarity index 96%
rename from readthedocs/templates/errors/404/dns.html
rename to readthedocs/templates/errors/proxito/404/dns.html
index 30ce574516a..62c0670f812 100644
--- a/readthedocs/templates/errors/404/dns.html
+++ b/readthedocs/templates/errors/proxito/404/dns.html
@@ -1,4 +1,4 @@
-{% extends "errors/404/base.html" %}
+{% extends "errors/proxito/404/base.html" %}
{% load core_tags %}
{% load i18n %}
diff --git a/readthedocs/templates/errors/404/include_error_message.html b/readthedocs/templates/errors/proxito/404/include_error_message.html
similarity index 100%
rename from readthedocs/templates/errors/404/include_error_message.html
rename to readthedocs/templates/errors/proxito/404/include_error_message.html
diff --git a/readthedocs/templates/errors/404/include_search.html b/readthedocs/templates/errors/proxito/404/include_search.html
similarity index 100%
rename from readthedocs/templates/errors/404/include_search.html
rename to readthedocs/templates/errors/proxito/404/include_search.html
diff --git a/readthedocs/templates/errors/404/include_tips.html b/readthedocs/templates/errors/proxito/404/include_tips.html
similarity index 100%
rename from readthedocs/templates/errors/404/include_tips.html
rename to readthedocs/templates/errors/proxito/404/include_tips.html
diff --git a/readthedocs/templates/errors/proxito/404/no_language.html b/readthedocs/templates/errors/proxito/404/no_language.html
new file mode 100644
index 00000000000..285fe29fc0a
--- /dev/null
+++ b/readthedocs/templates/errors/proxito/404/no_language.html
@@ -0,0 +1,10 @@
+{% extends "errors/proxito/404/base.html" %}
+{% load i18n %}
+
+{# project exists but not in the requested language #}
+
+{% block 404_error_message %}
+ {% include "errors/proxito/404/include_error_message.html" %}
+ {% include "errors/proxito/404/include_search.html" with search_project=project.slug %}
+ {% include "errors/proxito/404/include_tips.html" %}
+{% endblock %}
diff --git a/readthedocs/templates/errors/404/no_project.html b/readthedocs/templates/errors/proxito/404/no_project.html
similarity index 80%
rename from readthedocs/templates/errors/404/no_project.html
rename to readthedocs/templates/errors/proxito/404/no_project.html
index 376c49d0606..e9f3d2c2743 100644
--- a/readthedocs/templates/errors/404/no_project.html
+++ b/readthedocs/templates/errors/proxito/404/no_project.html
@@ -1,4 +1,4 @@
-{% extends "errors/404/base.html" %}
+{% extends "errors/proxito/404/base.html" %}
{% load i18n %}
{# visiting a slug or subdomain that doesn't match a project #}
@@ -12,5 +12,5 @@
- {% include "errors/404/include_search.html" %}
+ {% include "errors/proxito/404/include_search.html" %}
{% endblock %}
diff --git a/readthedocs/templates/errors/proxito/404/no_project_page.html b/readthedocs/templates/errors/proxito/404/no_project_page.html
new file mode 100644
index 00000000000..52d6d456d96
--- /dev/null
+++ b/readthedocs/templates/errors/proxito/404/no_project_page.html
@@ -0,0 +1,10 @@
+{% extends "errors/proxito/404/base.html" %}
+{% load i18n %}
+
+{# project exists but page doesn't #}
+
+{% block 404_error_message %}
+ {% include "errors/proxito/404/include_error_message.html" %}
+ {% include "errors/proxito/404/include_search.html" with search_project=project.slug %}
+ {% include "errors/proxito/404/include_tips.html" %}
+{% endblock %}
diff --git a/readthedocs/templates/errors/proxito/404/no_subproject.html b/readthedocs/templates/errors/proxito/404/no_subproject.html
new file mode 100644
index 00000000000..82d5c7538b2
--- /dev/null
+++ b/readthedocs/templates/errors/proxito/404/no_subproject.html
@@ -0,0 +1,10 @@
+{% extends "errors/proxito/404/base.html" %}
+{% load i18n %}
+
+{# project exists but subproject doesn't #}
+
+{% block 404_error_message %}
+ {% include "errors/proxito/404/include_error_message.html" %}
+ {% include "errors/proxito/404/include_search.html" with search_project=project.slug %}
+ {% include "errors/proxito/404/include_tips.html" %}
+{% endblock %}
diff --git a/readthedocs/templates/errors/proxito/404/no_version.html b/readthedocs/templates/errors/proxito/404/no_version.html
new file mode 100644
index 00000000000..73faa38e9e5
--- /dev/null
+++ b/readthedocs/templates/errors/proxito/404/no_version.html
@@ -0,0 +1,10 @@
+{% extends "errors/proxito/404/base.html" %}
+{% load i18n %}
+
+{# project exists but version doesn't #}
+
+{% block 404_error_message %}
+ {% include "errors/proxito/404/include_error_message.html" %}
+ {% include "errors/proxito/404/include_search.html" with search_project=project.slug %}
+ {% include "errors/proxito/404/include_tips.html" %}
+{% endblock %}
diff --git a/readthedocs/templates/core/teapot.html b/readthedocs/templates/errors/proxito/418.html
similarity index 81%
rename from readthedocs/templates/core/teapot.html
rename to readthedocs/templates/errors/proxito/418.html
index c9d8ec152ff..4d5478629aa 100644
--- a/readthedocs/templates/core/teapot.html
+++ b/readthedocs/templates/errors/proxito/418.html
@@ -1,4 +1,5 @@
-{% extends "errors/base.html" %}
+{% extends "errors/proxito/4xx.html" %}
+
{% load core_tags %}
{% load i18n %}
diff --git a/readthedocs/templates/errors/proxito/429.html b/readthedocs/templates/errors/proxito/429.html
new file mode 120000
index 00000000000..94d8439a50a
--- /dev/null
+++ b/readthedocs/templates/errors/proxito/429.html
@@ -0,0 +1 @@
+../dashboard/429.html
\ No newline at end of file
diff --git a/readthedocs/templates/errors/proxito/4xx.html b/readthedocs/templates/errors/proxito/4xx.html
new file mode 100644
index 00000000000..ec09794f0d4
--- /dev/null
+++ b/readthedocs/templates/errors/proxito/4xx.html
@@ -0,0 +1,12 @@
+{% extends "errors/proxito/base.html" %}
+
+{% load core_tags %}
+{% load i18n %}
+
+{% block title %}
+ {% trans "Bad request" %}
+{% endblock %}
+
+{% block content %}
+ {% trans "Bad request" %}
+{% endblock %}
diff --git a/readthedocs/templates/errors/proxito/500.html b/readthedocs/templates/errors/proxito/500.html
new file mode 120000
index 00000000000..baae7d90a5a
--- /dev/null
+++ b/readthedocs/templates/errors/proxito/500.html
@@ -0,0 +1 @@
+../dashboard/500.html
\ No newline at end of file
diff --git a/readthedocs/templates/errors/proxito/5xx.html b/readthedocs/templates/errors/proxito/5xx.html
new file mode 120000
index 00000000000..baae7d90a5a
--- /dev/null
+++ b/readthedocs/templates/errors/proxito/5xx.html
@@ -0,0 +1 @@
+../dashboard/500.html
\ No newline at end of file
diff --git a/readthedocs/templates/errors/proxito/base.html b/readthedocs/templates/errors/proxito/base.html
new file mode 100644
index 00000000000..d6dd60f4cfa
--- /dev/null
+++ b/readthedocs/templates/errors/proxito/base.html
@@ -0,0 +1 @@
+{% extends "errors/base.html" %}
diff --git a/readthedocs/templates/errors/proxito/spam.html b/readthedocs/templates/errors/proxito/spam.html
new file mode 120000
index 00000000000..3fe7b9eac4d
--- /dev/null
+++ b/readthedocs/templates/errors/proxito/spam.html
@@ -0,0 +1 @@
+../dashboard/spam.html
\ No newline at end of file
diff --git a/readthedocs/urls.py b/readthedocs/urls.py
index 4abd1349b65..e98387db4c3 100644
--- a/readthedocs/urls.py
+++ b/readthedocs/urls.py
@@ -122,9 +122,9 @@
TemplateView.as_view(template_name="style_catalog.html"),
),
# For testing error responses and templates
- path(
- "error//",
- ErrorView.as_view(base_path="errors/dashboard"),
+ re_path(
+ r"^error/(?P.*)$",
+ ErrorView.as_view(),
),
# This must come last after the build output files
path(