Allow custom 404.html on projects
Serve `404.html` from default version configured in the project if exists.
@@ -12,15 +12,19 @@ import os
import logging
from django.conf import settings
-from django.http import HttpResponseRedirect, Http404, JsonResponse
+from django.http import HttpResponseRedirect, Http404, JsonResponse, HttpResponse
from django.shortcuts import render, get_object_or_404, redirect
from django.views.generic import TemplateView
+
from readthedocs.builds.models import Version
+from readthedocs.core.resolver import resolve_path
+from readthedocs.core.symlink import PrivateSymlink, PublicSymlink
from readthedocs.core.utils import broadcast
+from readthedocs.projects.constants import PRIVATE
from readthedocs.projects.models import Project, ImportedFile
from readthedocs.projects.tasks import remove_dirs
-from readthedocs.redirects.utils import get_redirect_response
+from readthedocs.redirects.utils import get_redirect_response, project_and_path_from_request
log = logging.getLogger(__name__)
@@ -115,6 +119,7 @@ def server_error_404(request, exception=None, template_name='404.html'): # pyli
"""
response = get_redirect_response(request, full_path=request.get_full_path())
+ # Return a redirect response if there is one
if response:
if response.url == request.build_absolute_uri():
# check that we do have a response and avoid infinite redirect
@@ -124,7 +129,36 @@ def server_error_404(request, exception=None, template_name='404.html'): # pyli
)
else:
return response
+
+ project, path = project_and_path_from_request(request, request.get_full_path())
+ version_slug = project.get_default_version()
+ version = project.versions.get(slug=version_slug)
+
+ filename = resolve_path(
+ project,
+ version_slug=version_slug,
+ filename='404.html',
+ subdomain=True, # subdomain will make it a "full" path without a URL prefix
+ )
+
+ # This breaks path joining, by ignoring the root when given an "absolute" path
+ if filename[0] == '/':
+ filename = filename[1:]
+
+ if version.privacy_level == PRIVATE:
+ symlink = PrivateSymlink(project)
+ else:
+ symlink = PublicSymlink(project)
+ basepath = symlink.project_root
+ fullpath = os.path.join(basepath, filename)
+
+ if os.path.exists(fullpath):
+ log.debug('Serving custom 404.html page for project: %', project.slug)
+ r = HttpResponse(open(fullpath).read(), content_type='text/html')
+ else:
+ # Return the default 404 page generated by Read the Docs
r = render(request, template_name)
+
r.status_code = 404
return r
