From 55cc8e9e2771f898affbf19df12c10d8950df43e Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Sat, 19 Jan 2019 17:56:59 +0100 Subject: [PATCH 01/17] Allow custom 404.html on projects Serve `404.html` from default version configured in the project if exists. --- readthedocs/core/views/__init__.py | 40 +++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/readthedocs/core/views/__init__.py b/readthedocs/core/views/__init__.py index 334a6040b00..61588b37002 100644 --- a/readthedocs/core/views/__init__.py +++ b/readthedocs/core/views/__init__.py @@ -12,15 +12,19 @@ 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 - r = render(request, template_name) + + 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 From a00c9f50952fadead13a922439491a9208f9518d Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Mon, 21 Jan 2019 15:40:58 +0100 Subject: [PATCH 02/17] Documentation guide for custom 404 page --- docs/guides/custom-404-page.rst | 41 +++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 docs/guides/custom-404-page.rst diff --git a/docs/guides/custom-404-page.rst b/docs/guides/custom-404-page.rst new file mode 100644 index 00000000000..7000663b6ff --- /dev/null +++ b/docs/guides/custom-404-page.rst @@ -0,0 +1,41 @@ +Use custom 404 page on my project +================================= + +If you want your project to use a custom page for not found pages instead of the "Maze Found" default one, +you can put a ``404.html`` on the root of your project's output. + +Once a 404 is about to be returned, Read the Docs first checks if there is a ``404.html`` in the root of your project's output and if it does exist, it's returned. + +As the ``404.html`` will be returned for all the URLs where the real page was not found, +all its resources URLs and links must be absolute (including internals), +otherwise they won't be found by the client for all the 404 URLs producing errors while displaying the page (styles and/or images not found). + +In case you want to follow the same style for the 404 page than your theme, you can either: + +1. manually copy the source of any already rendered pages or, +1. use the `sphinx-notfound-page`_ extension + + +Manually creation +----------------- + +Once your docs are built, you can open any of the page already built and copy its source code, +make all the links absolute and replace the content of the body with the one you like. + +.. warning:: + + This method requires knowledge of HTML and some knowledge of how to build the URLs properly to work under Read the Docs, + considering that the docs are usually served under ``/{language}/{version}/``. + +After that, you have to define `html_extra_path`_ setting in your Sphinx's ``conf.py`` to include the ``404.html`` file created in the output. + + +Using ``sphinx-notfound-page`` extension +---------------------------------------- + +The ``sphinx-notfound-page`` extension helps you to create and automatically arrange all the URLs and file location without worry about them. +You can define ``notfound_body`` setting in your Sphinx's ``conf.py`` with the content of the page. + + +.. _sphinx-notfound-page: https://github.com/humitos/sphinx-notfound-page +.. _html_extra_path: http://www.sphinx-doc.org/en/stable/usage/configuration.html#confval-html_extra_path From 28b9a64c1f4d7fb2ea53e71bc5c8dce99f8df2c9 Mon Sep 17 00:00:00 2001 From: Eric Holscher <25510+ericholscher@users.noreply.github.com> Date: Thu, 31 Jan 2019 16:10:52 +0100 Subject: [PATCH 03/17] Update docs/guides/custom-404-page.rst Co-Authored-By: humitos --- docs/guides/custom-404-page.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guides/custom-404-page.rst b/docs/guides/custom-404-page.rst index 7000663b6ff..e3533f1ca78 100644 --- a/docs/guides/custom-404-page.rst +++ b/docs/guides/custom-404-page.rst @@ -4,7 +4,7 @@ Use custom 404 page on my project If you want your project to use a custom page for not found pages instead of the "Maze Found" default one, you can put a ``404.html`` on the root of your project's output. -Once a 404 is about to be returned, Read the Docs first checks if there is a ``404.html`` in the root of your project's output and if it does exist, it's returned. +When a 404 is returned, Read the Docs checks if there is a ``404.html`` in the root of your project's output and uses it if it exists. As the ``404.html`` will be returned for all the URLs where the real page was not found, all its resources URLs and links must be absolute (including internals), From 379186850485cd62e1010f5f7730e7b75c03cfbb Mon Sep 17 00:00:00 2001 From: Eric Holscher <25510+ericholscher@users.noreply.github.com> Date: Thu, 31 Jan 2019 16:11:25 +0100 Subject: [PATCH 04/17] Update docs/guides/custom-404-page.rst Co-Authored-By: humitos --- docs/guides/custom-404-page.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guides/custom-404-page.rst b/docs/guides/custom-404-page.rst index e3533f1ca78..df04be26b79 100644 --- a/docs/guides/custom-404-page.rst +++ b/docs/guides/custom-404-page.rst @@ -7,7 +7,7 @@ you can put a ``404.html`` on the root of your project's output. When a 404 is returned, Read the Docs checks if there is a ``404.html`` in the root of your project's output and uses it if it exists. As the ``404.html`` will be returned for all the URLs where the real page was not found, -all its resources URLs and links must be absolute (including internals), +all its resources URLs and links must be absolute (start with a `/`), otherwise they won't be found by the client for all the 404 URLs producing errors while displaying the page (styles and/or images not found). In case you want to follow the same style for the 404 page than your theme, you can either: From 66ffbe982b4781256c7447b1024e872af8ebb0a0 Mon Sep 17 00:00:00 2001 From: Eric Holscher <25510+ericholscher@users.noreply.github.com> Date: Thu, 31 Jan 2019 16:13:12 +0100 Subject: [PATCH 05/17] Update docs/guides/custom-404-page.rst Co-Authored-By: humitos --- docs/guides/custom-404-page.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guides/custom-404-page.rst b/docs/guides/custom-404-page.rst index df04be26b79..5472806e496 100644 --- a/docs/guides/custom-404-page.rst +++ b/docs/guides/custom-404-page.rst @@ -8,7 +8,7 @@ When a 404 is returned, Read the Docs checks if there is a ``404.html`` in the r As the ``404.html`` will be returned for all the URLs where the real page was not found, all its resources URLs and links must be absolute (start with a `/`), -otherwise they won't be found by the client for all the 404 URLs producing errors while displaying the page (styles and/or images not found). +otherwise they will not work when a user clicks on them. In case you want to follow the same style for the 404 page than your theme, you can either: From a33aee72b8bab0ae81283ce9c672a7ca0ab8d268 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Thu, 31 Jan 2019 16:23:02 +0100 Subject: [PATCH 06/17] Do not mention "Manually creation" of the 404 HTML --- docs/guides/custom-404-page.rst | 21 +++------------------ 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/docs/guides/custom-404-page.rst b/docs/guides/custom-404-page.rst index 5472806e496..1e98b477e13 100644 --- a/docs/guides/custom-404-page.rst +++ b/docs/guides/custom-404-page.rst @@ -10,24 +10,7 @@ As the ``404.html`` will be returned for all the URLs where the real page was no all its resources URLs and links must be absolute (start with a `/`), otherwise they will not work when a user clicks on them. -In case you want to follow the same style for the 404 page than your theme, you can either: - -1. manually copy the source of any already rendered pages or, -1. use the `sphinx-notfound-page`_ extension - - -Manually creation ------------------ - -Once your docs are built, you can open any of the page already built and copy its source code, -make all the links absolute and replace the content of the body with the one you like. - -.. warning:: - - This method requires knowledge of HTML and some knowledge of how to build the URLs properly to work under Read the Docs, - considering that the docs are usually served under ``/{language}/{version}/``. - -After that, you have to define `html_extra_path`_ setting in your Sphinx's ``conf.py`` to include the ``404.html`` file created in the output. +In case you want to follow the same style for the 404 page than your theme, you can use the `sphinx-notfound-page`_ extension. Using ``sphinx-notfound-page`` extension @@ -35,7 +18,9 @@ Using ``sphinx-notfound-page`` extension The ``sphinx-notfound-page`` extension helps you to create and automatically arrange all the URLs and file location without worry about them. You can define ``notfound_body`` setting in your Sphinx's ``conf.py`` with the content of the page. +See it's documentation_ for more customization. .. _sphinx-notfound-page: https://github.com/humitos/sphinx-notfound-page +.. _documentation: https://github.com/humitos/sphinx-notfound-page .. _html_extra_path: http://www.sphinx-doc.org/en/stable/usage/configuration.html#confval-html_extra_path From 8f94b289df52842010cfd69cde92172cfe0c923f Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Thu, 31 Jan 2019 16:39:02 +0100 Subject: [PATCH 07/17] Add docstring to readthedocs.core.views.serve._serve_file --- readthedocs/core/views/serve.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/readthedocs/core/views/serve.py b/readthedocs/core/views/serve.py index 58ad7fda93e..d756ab8f3ed 100644 --- a/readthedocs/core/views/serve.py +++ b/readthedocs/core/views/serve.py @@ -139,6 +139,23 @@ def _serve_401(request, project): def _serve_file(request, filename, basepath): + """ + Serve media file via Django or NGINX based on ``PYTHON_MEDIA``. + + When using ``PYTHON_MEDIA=True`` (or when ``DEBUG=True``) the file is served + by ``django.views.static.serve`` function. + + On the other hand, when ``PYTHON_MEDIA=False`` the file is served by using + ``X-Accel-Redirect`` header for NGINX to take care of it and serve the file. + + :param request: Django HTTP request + :param filename: path to the filename to be served relative to ``basepath`` + :param basepath: base path to prepend to the filename + + :returns: Django HTTP response object + + :raises: ``Http404`` on ``UnicodeEncodeError`` + """ # Serve the file from the proper location if settings.DEBUG or getattr(settings, 'PYTHON_MEDIA', False): # Serve from Python From c32422a5690a83ede41480fc60a1e9051f2e9e87 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Thu, 31 Jan 2019 16:39:25 +0100 Subject: [PATCH 08/17] Use _serve_file to serve custom 404 page --- readthedocs/core/views/__init__.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/readthedocs/core/views/__init__.py b/readthedocs/core/views/__init__.py index 61588b37002..d106da9fe50 100644 --- a/readthedocs/core/views/__init__.py +++ b/readthedocs/core/views/__init__.py @@ -12,7 +12,7 @@ import logging from django.conf import settings -from django.http import HttpResponseRedirect, Http404, JsonResponse, HttpResponse +from django.http import HttpResponseRedirect, Http404, JsonResponse from django.shortcuts import render, get_object_or_404, redirect from django.views.generic import TemplateView @@ -21,6 +21,7 @@ from readthedocs.core.resolver import resolve_path from readthedocs.core.symlink import PrivateSymlink, PublicSymlink from readthedocs.core.utils import broadcast +from readthedocs.core.views.serve import _serve_file from readthedocs.projects.constants import PRIVATE from readthedocs.projects.models import Project, ImportedFile from readthedocs.projects.tasks import remove_dirs @@ -153,8 +154,8 @@ def server_error_404(request, exception=None, template_name='404.html'): # pyli 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') + log.info('Serving custom 404.html page for project: %', project.slug) + r = _serve_file(request, filename, basepath) else: # Return the default 404 page generated by Read the Docs r = render(request, template_name) From 689ec78e722748cf581b510409e6688f3d8f1baa Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Thu, 31 Jan 2019 17:00:36 +0100 Subject: [PATCH 09/17] Serve custom 404 page only on subdomain and cname requests --- readthedocs/core/views/__init__.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/readthedocs/core/views/__init__.py b/readthedocs/core/views/__init__.py index d106da9fe50..a8d1253eaec 100644 --- a/readthedocs/core/views/__init__.py +++ b/readthedocs/core/views/__init__.py @@ -131,6 +131,24 @@ def server_error_404(request, exception=None, template_name='404.html'): # pyli else: return response + # Try to serve custom 404 pages if it's a subdomain/cname + if request.subdomain or request.cname: + return server_error_404_subdomain(request, template_name) + + # Return the default 404 page generated by Read the Docs + r = render(request, template_name) + r.status_code = 404 + return r + + +def server_error_404_subdomain(request, template_name='404.html'): + """ + Handler for 404 pages on subdomains. + + Check if the project associated has a custom ``404.html`` on it's project's + root (default version) and serve it. Otherwise, fallback to the default + ``template_name`` rendered by Django. + """ 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) From 9ac46efaea490f6a463eca7f7a6b4dcb788a7937 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Mon, 4 Feb 2019 14:15:58 +0100 Subject: [PATCH 10/17] Use sphinx-notfound-page by default to create a generic 404 page We use the sphinx-notfound-page extension to create a generic page for each project. By default we use the same Maze Found ASCII art, but the user can customize it by defining specific configs from the sphinx-notfound-page. --- readthedocs/core/views/__init__.py | 2 +- readthedocs/doc_builder/python_environments.py | 8 ++++++-- .../doc_builder/templates/doc_builder/conf.py.tmpl | 13 +++++++++++++ readthedocs/rtd_tests/tests/test_doc_building.py | 2 ++ 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/readthedocs/core/views/__init__.py b/readthedocs/core/views/__init__.py index a8d1253eaec..7f093e1a804 100644 --- a/readthedocs/core/views/__init__.py +++ b/readthedocs/core/views/__init__.py @@ -132,7 +132,7 @@ def server_error_404(request, exception=None, template_name='404.html'): # pyli return response # Try to serve custom 404 pages if it's a subdomain/cname - if request.subdomain or request.cname: + if getattr(request, 'subdomain', False) or getattr(request, 'cname', False): return server_error_404_subdomain(request, template_name) # Return the default 404 page generated by Read the Docs diff --git a/readthedocs/doc_builder/python_environments.py b/readthedocs/doc_builder/python_environments.py index a8353d5f4f7..92176885eda 100644 --- a/readthedocs/doc_builder/python_environments.py +++ b/readthedocs/doc_builder/python_environments.py @@ -307,6 +307,7 @@ def install_core_requirements(self): ), 'sphinx-rtd-theme<0.5', 'readthedocs-sphinx-ext<0.6', + 'sphinx-notfound-page', ]) cmd = copy.copy(pip_install_cmd) @@ -429,7 +430,10 @@ def install_core_requirements(self): if self.config.doctype == 'mkdocs': pip_requirements.append('mkdocs') else: - pip_requirements.append('readthedocs-sphinx-ext') + pip_requirements.extend([ + 'readthedocs-sphinx-ext', + 'sphinx-notfound-page', + ]) requirements.extend(['sphinx', 'sphinx_rtd_theme']) cmd = [ @@ -443,7 +447,7 @@ def install_core_requirements(self): cmd.extend(requirements) self.build_env.run( *cmd, - cwd=self.checkout_path # noqa - no comma here in py27 :/ + cwd=self.checkout_path, ) pip_cmd = [ diff --git a/readthedocs/doc_builder/templates/doc_builder/conf.py.tmpl b/readthedocs/doc_builder/templates/doc_builder/conf.py.tmpl index 7aeee3e0a45..6f3cb5e9b5f 100644 --- a/readthedocs/doc_builder/templates/doc_builder/conf.py.tmpl +++ b/readthedocs/doc_builder/templates/doc_builder/conf.py.tmpl @@ -140,3 +140,16 @@ if 'extensions' in globals(): extensions.insert(0, "readthedocs_ext.readthedocs") else: extensions = ["readthedocs_ext.readthedocs"] + + +# Define default 404 page body +if 'notfound_context' not in globals(): + notfound_context = { + 'body': ''' +

Page not found

+ +

Sorry, we couldn't find that page.

+ +

Try using the search box or go to the homepage.

+''', + } diff --git a/readthedocs/rtd_tests/tests/test_doc_building.py b/readthedocs/rtd_tests/tests/test_doc_building.py index 6f6028612d5..913a553a471 100644 --- a/readthedocs/rtd_tests/tests/test_doc_building.py +++ b/readthedocs/rtd_tests/tests/test_doc_building.py @@ -1229,6 +1229,7 @@ def test_install_core_requirements_sphinx(self, checkout_path): 'sphinx', 'sphinx-rtd-theme', 'readthedocs-sphinx-ext', + 'sphinx-notfound-page', ] requirements = self.base_requirements + requirements_sphinx args = self.pip_install_args + requirements @@ -1356,6 +1357,7 @@ def test_install_core_requirements_sphinx_conda(self, checkout_path): pip_requirements = [ 'recommonmark', 'readthedocs-sphinx-ext', + 'sphinx-notfound-page', ] args_pip = [ From fbfa68ebdeff01e64d3cde4dfd3e801bcdae5a3a Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Mon, 4 Feb 2019 18:54:40 +0100 Subject: [PATCH 11/17] Serve 404 page for current version first and fallback to default --- readthedocs/core/views/__init__.py | 91 ++++++++++++++++++++---------- 1 file changed, 60 insertions(+), 31 deletions(-) diff --git a/readthedocs/core/views/__init__.py b/readthedocs/core/views/__init__.py index 7f093e1a804..cf27c09c7c4 100644 --- a/readthedocs/core/views/__init__.py +++ b/readthedocs/core/views/__init__.py @@ -10,6 +10,7 @@ from __future__ import division import os import logging +from urllib.parse import urlparse from django.conf import settings from django.http import HttpResponseRedirect, Http404, JsonResponse @@ -25,7 +26,7 @@ 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, project_and_path_from_request +from readthedocs.redirects.utils import get_redirect_response, project_and_path_from_request, language_and_version_from_path log = logging.getLogger(__name__) @@ -145,39 +146,67 @@ def server_error_404_subdomain(request, template_name='404.html'): """ Handler for 404 pages on subdomains. - Check if the project associated has a custom ``404.html`` on it's project's - root (default version) and serve it. Otherwise, fallback to the default - ``template_name`` rendered by Django. + Check if the project associated has a custom ``404.html`` and serve this + page. First search for a 404 page in the current version, then continues + with the default version and finally, if none of them are found, the Read + the Docs default page (Maze Found) is rendered by Django and served. """ - 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.info('Serving custom 404.html page for project: %', project.slug) - r = _serve_file(request, filename, basepath) - else: - # Return the default 404 page generated by Read the Docs - r = render(request, template_name) + def resolve_404_path(project, version_slug=None, language=None): + """ + Helper to resolve the path of ``404.html`` for project. + + The resolution is based on ``project`` object, version slug and + language. + + :returns: tuple containing the (basepath, filename) + :rtype: tuple + """ + filename = resolve_path( + project, + version_slug=version_slug, + language=language, + 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:] + + version = project.versions.get(slug=version_slug) + if version.privacy_level == PRIVATE: + symlink = PrivateSymlink(project) + else: + symlink = PublicSymlink(project) + basepath = symlink.project_root + fullpath = os.path.join(basepath, filename) + return (basepath, filename, fullpath) + + project, full_path = project_and_path_from_request(request, request.get_full_path()) + + language = None + version_slug = None + schema, netloc, path, params, query, fragments = urlparse(full_path) + if not project.single_version: + language, version_slug, path = language_and_version_from_path(path) + + # Firstly, attempt to serve the 404 of the current version (version_slug) + # Secondly, try to serve the 404 page for the default version (project.get_default_version()) + for slug in (version_slug, project.get_default_version()): + basepath, filename, fullpath = resolve_404_path(project, slug, language) + if os.path.exists(fullpath): + log.debug( + 'serving 404.html page current version: [project: %s] [version: %s]', + project.slug, + slug, + ) + r = _serve_file(request, filename, basepath) + r.status_code = 404 + return r + # Finally, return the default 404 page generated by Read the Docs + r = render(request, template_name) r.status_code = 404 return r From d1d7e8185602c535889979a235781cfe0d92b7b9 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Mon, 4 Feb 2019 22:06:57 +0100 Subject: [PATCH 12/17] Test case for existing 404.html on project --- .../rtd_tests/tests/test_doc_serving.py | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/readthedocs/rtd_tests/tests/test_doc_serving.py b/readthedocs/rtd_tests/tests/test_doc_serving.py index d3e5ba74989..732b7e4554f 100644 --- a/readthedocs/rtd_tests/tests/test_doc_serving.py +++ b/readthedocs/rtd_tests/tests/test_doc_serving.py @@ -2,14 +2,17 @@ import django_dynamic_fixture as fixture import mock +import os from django.conf import settings from django.contrib.auth.models import User from django.http import Http404 -from django.test import TestCase +from django.test import TestCase, RequestFactory from django.test.utils import override_settings from django.urls import reverse from mock import mock_open, patch +from readthedocs.core.middleware import SubdomainMiddleware +from readthedocs.core.views import server_error_404_subdomain from readthedocs.core.views.serve import _serve_symlink_docs from readthedocs.projects import constants from readthedocs.projects.models import Project @@ -169,3 +172,31 @@ def test_custom_robots_txt(self, os_mock, open_mock): ) self.assertEqual(response.status_code, 200) self.assertEqual(response.content, b'My own robots.txt') + + @override_settings( + PYTHON_MEDIA=False, + USE_SUBDOMAIN=True, + PUBLIC_DOMAIN='readthedocs.io', + ROOT_URLCONF=settings.SUBDOMAIN_URLCONF, + ) + @patch('readthedocs.core.views.serve.os') + @patch('readthedocs.core.views.os') + def test_custom_404_page(self, os_view_mock, os_serve_mock): + os_view_mock.path.exists.return_value = True + + os_serve_mock.path.join.side_effect = os.path.join + os_serve_mock.path.exists.return_value = True + + self.public.versions.update(active=True, built=True) + + factory = RequestFactory() + request = factory.get( + '/en/latest/notfoundpage.html', + HTTP_HOST='public.readthedocs.io', + ) + + middleware = SubdomainMiddleware() + middleware.process_request(request) + response = server_error_404_subdomain(request) + self.assertEqual(response.status_code, 404) + self.assertTrue(response['X-Accel-Redirect'].endswith('/public/en/latest/404.html')) From ee2f595919204eeb34f55c3aead8ef11a67df1c6 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Tue, 5 Feb 2019 09:07:48 +0100 Subject: [PATCH 13/17] Fix privacy level for test cases --- readthedocs/core/views/__init__.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/readthedocs/core/views/__init__.py b/readthedocs/core/views/__init__.py index cf27c09c7c4..52e1f59a57a 100644 --- a/readthedocs/core/views/__init__.py +++ b/readthedocs/core/views/__init__.py @@ -174,8 +174,15 @@ def resolve_404_path(project, version_slug=None, language=None): if filename[0] == '/': filename = filename[1:] - version = project.versions.get(slug=version_slug) - if version.privacy_level == PRIVATE: + version = None + if version_slug: + version = project.versions.get(slug=version_slug) + + private = any([ + version and version.privacy_level == PRIVATE, + not version and project.privacy_level == PRIVATE, + ]) + if private: symlink = PrivateSymlink(project) else: symlink = PublicSymlink(project) From ca146163d266066247afdbe4631c794d14f4784e Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Wed, 6 Feb 2019 16:53:07 +0100 Subject: [PATCH 14/17] Revert "Use sphinx-notfound-page by default to create a generic 404 page" This reverts commit 9ac46efaea490f6a463eca7f7a6b4dcb788a7937. --- readthedocs/core/views/__init__.py | 2 +- readthedocs/doc_builder/python_environments.py | 8 ++------ .../doc_builder/templates/doc_builder/conf.py.tmpl | 13 ------------- readthedocs/rtd_tests/tests/test_doc_building.py | 2 -- 4 files changed, 3 insertions(+), 22 deletions(-) diff --git a/readthedocs/core/views/__init__.py b/readthedocs/core/views/__init__.py index 52e1f59a57a..a887b73d4a5 100644 --- a/readthedocs/core/views/__init__.py +++ b/readthedocs/core/views/__init__.py @@ -133,7 +133,7 @@ def server_error_404(request, exception=None, template_name='404.html'): # pyli return response # Try to serve custom 404 pages if it's a subdomain/cname - if getattr(request, 'subdomain', False) or getattr(request, 'cname', False): + if request.subdomain or request.cname: return server_error_404_subdomain(request, template_name) # Return the default 404 page generated by Read the Docs diff --git a/readthedocs/doc_builder/python_environments.py b/readthedocs/doc_builder/python_environments.py index 92176885eda..a8353d5f4f7 100644 --- a/readthedocs/doc_builder/python_environments.py +++ b/readthedocs/doc_builder/python_environments.py @@ -307,7 +307,6 @@ def install_core_requirements(self): ), 'sphinx-rtd-theme<0.5', 'readthedocs-sphinx-ext<0.6', - 'sphinx-notfound-page', ]) cmd = copy.copy(pip_install_cmd) @@ -430,10 +429,7 @@ def install_core_requirements(self): if self.config.doctype == 'mkdocs': pip_requirements.append('mkdocs') else: - pip_requirements.extend([ - 'readthedocs-sphinx-ext', - 'sphinx-notfound-page', - ]) + pip_requirements.append('readthedocs-sphinx-ext') requirements.extend(['sphinx', 'sphinx_rtd_theme']) cmd = [ @@ -447,7 +443,7 @@ def install_core_requirements(self): cmd.extend(requirements) self.build_env.run( *cmd, - cwd=self.checkout_path, + cwd=self.checkout_path # noqa - no comma here in py27 :/ ) pip_cmd = [ diff --git a/readthedocs/doc_builder/templates/doc_builder/conf.py.tmpl b/readthedocs/doc_builder/templates/doc_builder/conf.py.tmpl index 6f3cb5e9b5f..7aeee3e0a45 100644 --- a/readthedocs/doc_builder/templates/doc_builder/conf.py.tmpl +++ b/readthedocs/doc_builder/templates/doc_builder/conf.py.tmpl @@ -140,16 +140,3 @@ if 'extensions' in globals(): extensions.insert(0, "readthedocs_ext.readthedocs") else: extensions = ["readthedocs_ext.readthedocs"] - - -# Define default 404 page body -if 'notfound_context' not in globals(): - notfound_context = { - 'body': ''' -

Page not found

- -

Sorry, we couldn't find that page.

- -

Try using the search box or go to the homepage.

-''', - } diff --git a/readthedocs/rtd_tests/tests/test_doc_building.py b/readthedocs/rtd_tests/tests/test_doc_building.py index 913a553a471..6f6028612d5 100644 --- a/readthedocs/rtd_tests/tests/test_doc_building.py +++ b/readthedocs/rtd_tests/tests/test_doc_building.py @@ -1229,7 +1229,6 @@ def test_install_core_requirements_sphinx(self, checkout_path): 'sphinx', 'sphinx-rtd-theme', 'readthedocs-sphinx-ext', - 'sphinx-notfound-page', ] requirements = self.base_requirements + requirements_sphinx args = self.pip_install_args + requirements @@ -1357,7 +1356,6 @@ def test_install_core_requirements_sphinx_conda(self, checkout_path): pip_requirements = [ 'recommonmark', 'readthedocs-sphinx-ext', - 'sphinx-notfound-page', ] args_pip = [ From 71d5ec14fb589ec9363f2be88a156d2ae5e75045 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Wed, 6 Feb 2019 16:54:18 +0100 Subject: [PATCH 15/17] Use getattr for checking subdomain and cname on request --- readthedocs/core/views/__init__.py | 2 +- readthedocs/doc_builder/python_environments.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/readthedocs/core/views/__init__.py b/readthedocs/core/views/__init__.py index a887b73d4a5..52e1f59a57a 100644 --- a/readthedocs/core/views/__init__.py +++ b/readthedocs/core/views/__init__.py @@ -133,7 +133,7 @@ def server_error_404(request, exception=None, template_name='404.html'): # pyli return response # Try to serve custom 404 pages if it's a subdomain/cname - if request.subdomain or request.cname: + if getattr(request, 'subdomain', False) or getattr(request, 'cname', False): return server_error_404_subdomain(request, template_name) # Return the default 404 page generated by Read the Docs diff --git a/readthedocs/doc_builder/python_environments.py b/readthedocs/doc_builder/python_environments.py index a8353d5f4f7..e117bbcca15 100644 --- a/readthedocs/doc_builder/python_environments.py +++ b/readthedocs/doc_builder/python_environments.py @@ -443,7 +443,7 @@ def install_core_requirements(self): cmd.extend(requirements) self.build_env.run( *cmd, - cwd=self.checkout_path # noqa - no comma here in py27 :/ + cwd=self.checkout_path, ) pip_cmd = [ From c09ad34e358b39cbd6f2d8769e938bdd7d0e32e0 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Wed, 6 Feb 2019 16:58:22 +0100 Subject: [PATCH 16/17] Build our docs with a custom 404 page It uses the external sphinx-notfound-page extension for this. --- docs/conf.py | 12 ++++++++++++ requirements/local-docs-build.txt | 3 ++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index d5de83408af..2335fbf81b8 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -81,6 +81,18 @@ # Activate autosectionlabel plugin autosectionlabel_prefix_document = True +# sphinx-notfound-page +# https://github.com/rtfd/sphinx-notfound-page +notfound_context = { + 'body': ''' +

Page not found

+ +

Sorry, we couldn't find that page.

+ +

Try using the search box or go to the homepage.

+''', +} + def setup(app): app.add_stylesheet('css/sphinx_prompt_css.css') diff --git a/requirements/local-docs-build.txt b/requirements/local-docs-build.txt index 80e50c95ecc..f40fb6d7254 100644 --- a/requirements/local-docs-build.txt +++ b/requirements/local-docs-build.txt @@ -1,6 +1,6 @@ -r pip.txt -# Base packages +# Base packages docutils==0.14 Sphinx==1.8.3 sphinx_rtd_theme==0.4.2 @@ -17,5 +17,6 @@ Markdown==3.0.1 # Docs sphinxcontrib-httpdomain==1.7.0 sphinx-prompt==1.0.0 +sphinx-notfound-page==0.1 commonmark==0.8.1 recommonmark==0.5.0 From a8f548a7699834b29377a2e7ca470476e1ef8d54 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Wed, 6 Feb 2019 17:01:21 +0100 Subject: [PATCH 17/17] Remove half backed documentation for custom 404 pages --- docs/guides/custom-404-page.rst | 26 -------------------------- 1 file changed, 26 deletions(-) delete mode 100644 docs/guides/custom-404-page.rst diff --git a/docs/guides/custom-404-page.rst b/docs/guides/custom-404-page.rst deleted file mode 100644 index 1e98b477e13..00000000000 --- a/docs/guides/custom-404-page.rst +++ /dev/null @@ -1,26 +0,0 @@ -Use custom 404 page on my project -================================= - -If you want your project to use a custom page for not found pages instead of the "Maze Found" default one, -you can put a ``404.html`` on the root of your project's output. - -When a 404 is returned, Read the Docs checks if there is a ``404.html`` in the root of your project's output and uses it if it exists. - -As the ``404.html`` will be returned for all the URLs where the real page was not found, -all its resources URLs and links must be absolute (start with a `/`), -otherwise they will not work when a user clicks on them. - -In case you want to follow the same style for the 404 page than your theme, you can use the `sphinx-notfound-page`_ extension. - - -Using ``sphinx-notfound-page`` extension ----------------------------------------- - -The ``sphinx-notfound-page`` extension helps you to create and automatically arrange all the URLs and file location without worry about them. -You can define ``notfound_body`` setting in your Sphinx's ``conf.py`` with the content of the page. -See it's documentation_ for more customization. - - -.. _sphinx-notfound-page: https://github.com/humitos/sphinx-notfound-page -.. _documentation: https://github.com/humitos/sphinx-notfound-page -.. _html_extra_path: http://www.sphinx-doc.org/en/stable/usage/configuration.html#confval-html_extra_path