diff --git a/media/css/readthedocs-doc-embed.css b/media/css/readthedocs-doc-embed.css deleted file mode 100644 index 6a2ccd64a1d..00000000000 --- a/media/css/readthedocs-doc-embed.css +++ /dev/null @@ -1,301 +0,0 @@ -/* Left for CSS overrides we need to make in the future */ - -/* Fix badge on RTD Theme */ - -/* Please keep RTD badge displayed on your site */ -.rst-versions.rst-badge { - display: block; - - bottom: 50px; - - /* Workaround for mkdocs which set a specific height for this element */ - height: auto; -} - -.rst-other-versions { - text-align: left; -} - -.rst-other-versions a { - border: 0; -} - -.rst-other-versions dl { - margin: 0; -} - -.rtd-current-item { - font-weight: bold; -} - - -/* Fix RTD theme bottom margin */ -.rst-content .line-block { - margin-bottom: 24px -} - -/* Fix for nav bottom padding with flyout */ -nav.wy-nav-side { - padding-bottom: 3em; -} - -/* bookmark icon */ -.bookmark-added-msg {display: none;} -.bookmark-active {display: none;} -.bookmark-inactive {display: none;} - - -/* Read the Docs promotional block, only applicable to RTD.org - -To support sphinx_rtd_theme, a `wy-menu` element is added. Other themes are -targeted using the theme identifier and use custom elements instead of a CSS -framework html structure. - -*/ - -div.ethical-sidebar, -div.ethical-footer { - display: block !important; -} -.ethical-sidebar, -.ethical-footer { - padding: 0.5em; - margin: 1em 0; -} -.ethical-sidebar img, -.ethical-footer img { - width: 120px; - height: 90px; - display: inline-block; -} -.ethical-sidebar .ethical-callout, -.ethical-footer .ethical-callout { - padding-top: 1em; - clear: both; -} -.ethical-sidebar .ethical-pixel, -.ethical-footer .ethical-pixel, -.ethical-fixedfooter .ethical-pixel { - display: none !important; -} -.ethical-sidebar .ethical-text, -.ethical-footer .ethical-text { - margin-top: 1em; -} -.ethical-sidebar .ethical-image-link, -.ethical-footer .ethical-image-link { - border: 0; -} - -.ethical-sidebar, -.ethical-footer { - background-color: #eee; - border: 1px solid #ccc; - border-radius: 5px; - color: #0a0a0a; - font-size: 14px; - line-height: 20px; -} - -/* Techstack badging */ -.ethical-sidebar ul { - margin: 0 !important; - padding-left: 0; - list-style: none; -} -.ethical-sidebar ul li { - display: inline-block; - background-color: lightskyblue; - color: black; - padding: 0.25em 0.4em; - font-size: 75%; - font-weight: 700; - margin: 0.25em; - border-radius: 0.25rem; - text-align: center; - vertical-align: baseline; - white-space: nowrap; - line-height: 1.41; -} -.ethical-sidebar ul li:not(:last-child) { - margin-right: .25rem; -} - -.ethical-sidebar a, -.ethical-sidebar a:visited, -.ethical-sidebar a:hover, -.ethical-sidebar a:active, -.ethical-footer a, -.ethical-footer a:visited, -.ethical-footer a:hover, -.ethical-footer a:active { - color: #0a0a0a; - text-decoration: none !important; - border-bottom: 0 !important; -} - -.ethical-callout a { - color: #707070 !important; - text-decoration: none !important; -} - -/* Sidebar promotions */ -.ethical-sidebar { - text-align: center; - max-width: 300px; - margin-left: auto; - margin-right: auto; -} - -/* Footer promotions */ -.ethical-footer { - text-align: left; - - font-size: 14px; - line-height: 20px; -} -.ethical-footer img { - float: right; - margin-left: 25px; -} -.ethical-footer .ethical-callout { - text-align: center; -} -.ethical-footer small { - font-size: 10px; -} - -/* Fixed footer promotions */ -.ethical-fixedfooter { - box-sizing: border-box; - position: fixed; - bottom: 0; - left: 0; - z-index: 100; - background-color: #eee; - border-top: 1px solid #bfbfbf; - font-size: 12px; - line-height: 1.5; - padding: 0.5em 1.5em; - text-align: center; - color: #404040; - width: 100%; /* Fallback for Opera Mini */ - width: 100vw; -} -@media (min-width: 769px) { - /* Improve viewing on non-mobile */ - .ethical-fixedfooter { - font-size: 13px; - padding: 1em 1.5em; - } -} -.ethical-fixedfooter .ethical-text:before { - margin-right: 4px; - padding: 2px 6px; - border-radius: 3px; - background-color: #4caf50; - color: #fff; - content: "Sponsored"; -} -.ethical-fixedfooter .ethical-callout { - color: #999; - padding-left: 6px; - white-space: nowrap; -} -.ethical-fixedfooter a, -.ethical-fixedfooter a:hover, -.ethical-fixedfooter a:active, -.ethical-fixedfooter a:visited { - color: #404040; - text-decoration: none; -} -.ethical-fixedfooter .ethical-close { - position: absolute; - top: 0; - right: 5px; - font-size: 20px; - line-height: 20px; -} - -/* RTD Theme specific customizations */ -.wy-nav-side .ethical-rtd { - /* RTD theme doesn't correctly set the sidebar width */ - max-width: 300px; - padding: 0 1em; -} -.ethical-rtd .ethical-sidebar { - /* RTD theme doesn't set sidebar text color */ - color: #b3b3b3; - - font-size: 14px; - line-height: 20px; -} - -@media (min-width: 769px) { - /* Make sure the fixed footer ad is under the RTD theme version selector */ - .wy-body-for-nav .ethical-fixedfooter { - padding-left: 300px; - } -} - -/* Alabaster specific customizations */ -.ethical-alabaster a.ethical-image-link { - /* Alabaster adds a border even to image links on hover */ - border: 0 !important; -} -.ethical-alabaster hr { - /* Alabaster needs some extra spacing before the footer ad */ - margin-top: 2em; -} -.ethical-alabaster::before { - /* Alabaster's search box above the ad is floating */ - clear: both; - content: ''; - display: table; - margin-top: 3em; -} - -/* Dark theme */ -.ethical-dark-theme .ethical-sidebar { - background-color: #4e4b4b; - border: 1px solid #a0a0a0; - color: #c2c2c2 !important; -} -.ethical-dark-theme a, -.ethical-dark-theme a:visited { - color: #e6e6e6 !important; - border-bottom: 0 !important; -} -.ethical-dark-theme .ethical-callout a { - color: #b3b3b3 !important; -} - - -/* Ad block nag */ -.keep-us-sustainable { - padding: .5em; - margin: 1em auto; - text-align: center; - border: 1px dotted #8ECC4C; - max-width: 300px; -} -.keep-us-sustainable a, -.keep-us-sustainable a:hover, -.keep-us-sustainable a:visited { - text-decoration: none; -} -/* Read the Docs theme specific fixes */ -.wy-nav-side .keep-us-sustainable { - margin: 1em 2em 1em 1em; - color: #b3b3b3; -} -.wy-nav-side .keep-us-sustainable a { - color: #efefef; - font-size: 14px; - line-height: 20px; -} - -/* Margin between the search results */ -.rtd_search_hits_spacing { - margin: 10px 0; -} diff --git a/readthedocs/api/v2/permissions.py b/readthedocs/api/v2/permissions.py index 8bc30ecd4e2..03a486116f2 100644 --- a/readthedocs/api/v2/permissions.py +++ b/readthedocs/api/v2/permissions.py @@ -29,7 +29,7 @@ class IsAuthorizedToViewVersion(permissions.BasePermission): """ Checks if the user from the request has permissions to see the version. - This permission class used in the FooterHTML and PageSearchAPIView views. + This permission class used in PageSearchAPIView views. .. note:: diff --git a/readthedocs/api/v2/proxied_urls.py b/readthedocs/api/v2/proxied_urls.py index 031f44af303..e527e37dfb1 100644 --- a/readthedocs/api/v2/proxied_urls.py +++ b/readthedocs/api/v2/proxied_urls.py @@ -8,14 +8,13 @@ from django.urls import path from readthedocs.analytics.proxied_api import AnalyticsView -from readthedocs.api.v2.views.proxied import ProxiedEmbedAPI, ProxiedFooterHTML +from readthedocs.api.v2.views.proxied import ProxiedEmbedAPI from readthedocs.search.api.v2.views import ProxiedPageSearchAPIView -api_footer_urls = [ - path("footer_html/", ProxiedFooterHTML.as_view(), name="footer_html"), +api_proxied_urls = [ path("search/", ProxiedPageSearchAPIView.as_view(), name="search_api"), path("embed/", ProxiedEmbedAPI.as_view(), name="embed_api"), path("analytics/", AnalyticsView.as_view(), name="analytics_api"), ] -urlpatterns = api_footer_urls +urlpatterns = api_proxied_urls diff --git a/readthedocs/api/v2/templates/restapi/footer.html b/readthedocs/api/v2/templates/restapi/footer.html deleted file mode 100644 index ef6b23aba0d..00000000000 --- a/readthedocs/api/v2/templates/restapi/footer.html +++ /dev/null @@ -1,145 +0,0 @@ - -{% load i18n %} -
- - {% if not new_theme %} -
- -   - v: {{ current_version.explicit_name }} - - -
- {% endif %} - - {% block versions %} - {% if translations %} -
-
{% trans "Languages" %}
- - {# Output the main project language since it isn't included in translations list #} - -
- {{ main_project.language }} -
- - {# regroup to make language_list unique per language #} - {% regroup translations by language as language_list %} - {% for group in language_list %} - {% for translation in group.list %} - {% if translation.language != main_project.language %} -
- {{ translation.language }} -
- {% endif %} - {% endfor %} - {% endfor %} - -
- {% endif %} - {% if project.supports_multiple_versions and versions|length >= 1 %} -
-
{% trans "Versions" %}
- {% for version in versions %} -
- {{ version.verbose_name }} -
- {% endfor %} -
- {% endif %} - {% endblock %} - - {% block downloads %} - {% if downloads %} -
-
{% trans "Downloads" %}
- {% for name, url in downloads.items %} -
{{ name }}
- {% endfor %} -
- {% endif %} - {% endblock %} - - {% block readthedocs %} -
- {# We hardcode the URLS because we don't have access to the URLCONF of the main app from proxito #} - -
{% trans "On Read the Docs" %}
-
- {% trans "Project Home" %} -
-
- {% trans "Builds" %} -
-
- {% trans "Downloads" %} -
-
- {% endblock %} - - {% block vcs %} - - {% if github_edit_url %} -
-
{% trans "On GitHub" %}
-
- {% trans "View" %} -
- {% if version.is_editable %} -
- {% trans "Edit" %} -
- {% endif %} -
- {% elif bitbucket_url %} -
-
{% trans "On Bitbucket" %}
-
- {% trans "Edit" %} -
-
- {% elif gitlab_edit_url %} -
-
{% trans "On GitLab" %}
-
- {% trans "View" %} -
- {% if version.is_editable %} -
- {% trans "Edit" %} -
- {% endif %} -
- {% endif %} - {% endblock %} - - {% block search %} -
-
{% trans "Search" %}
-
-
- {# We hardcode the URLS because we don't have access to the URLCONF of the main app from proxito #} -
- -
-
-
-
- {% endblock %} - -
- - {% block footer %} - - {% trans "Hosted by" %} Read the Docs - · - {% trans "Privacy Policy" %} - - {% endblock %} - - {% if not new_theme %} -
-
- {% endif %} - -
diff --git a/readthedocs/api/v2/urls.py b/readthedocs/api/v2/urls.py index 673b2b6f00f..a7359a6b049 100644 --- a/readthedocs/api/v2/urls.py +++ b/readthedocs/api/v2/urls.py @@ -3,7 +3,7 @@ from django.urls import include, path, re_path from rest_framework import routers -from readthedocs.api.v2.views import core_views, footer_views, integrations, task_views +from readthedocs.api.v2.views import core_views, integrations, task_views from readthedocs.constants import pattern_opts from readthedocs.gold.views import StripeEventView @@ -56,7 +56,6 @@ function_urls = [ path("docurl/", core_views.docurl, name="docurl"), - path("footer_html/", footer_views.FooterHTML.as_view(), name="footer_html"), ] task_urls = [ diff --git a/readthedocs/api/v2/views/footer_views.py b/readthedocs/api/v2/views/footer_views.py deleted file mode 100644 index 856b6b00b01..00000000000 --- a/readthedocs/api/v2/views/footer_views.py +++ /dev/null @@ -1,233 +0,0 @@ -"""Endpoint to generate footer HTML.""" - -import re -from functools import lru_cache - -import structlog -from django.conf import settings -from django.shortcuts import get_object_or_404 -from django.template import loader as template_loader -from rest_framework.renderers import JSONRenderer -from rest_framework.response import Response -from rest_framework.views import APIView -from rest_framework_jsonp.renderers import JSONPRenderer - -from readthedocs.api.mixins import CDNCacheTagsMixin -from readthedocs.api.v2.permissions import IsAuthorizedToViewVersion -from readthedocs.builds.constants import LATEST, TAG -from readthedocs.builds.models import Version -from readthedocs.core.utils.extend import SettingsOverrideObject -from readthedocs.projects.constants import MKDOCS, SPHINX_HTMLDIR -from readthedocs.projects.models import Project -from readthedocs.projects.version_handling import ( - highest_version, - parse_version_failsafe, -) - -log = structlog.get_logger(__name__) - - -def get_version_compare_data(project, base_version=None, user=None): - """ - Retrieve metadata about the highest version available for this project. - - :param base_version: We assert whether or not the base_version is also the - highest version in the resulting "is_highest" value. - """ - if not project.show_version_warning or (base_version and base_version.is_external): - return {"is_highest": False} - - versions_qs = Version.internal.public(project=project, user=user).filter( - built=True, active=True - ) - - # Take preferences over tags only if the project has at least one tag - if versions_qs.filter(type=TAG).exists(): - versions_qs = versions_qs.filter(type=TAG) - - # Optimization - versions_qs = versions_qs.select_related("project") - - highest_version_obj, highest_version_comparable = highest_version( - versions_qs, - ) - ret_val = { - "project": str(highest_version_obj), - "version": str(highest_version_comparable), - "is_highest": True, - } - if highest_version_obj: - # Never link to the dashboard, - # users reading the docs may don't have access to the dashboard. - ret_val["url"] = highest_version_obj.get_absolute_url() - ret_val["slug"] = highest_version_obj.slug - if base_version and base_version.slug != LATEST: - try: - base_version_comparable = parse_version_failsafe( - base_version.verbose_name, - ) - if base_version_comparable: - # This is only place where is_highest can get set. All error - # cases will be set to True, for non- standard versions. - ret_val["is_highest"] = ( - base_version_comparable >= highest_version_comparable - ) - else: - ret_val["is_highest"] = True - except (Version.DoesNotExist, TypeError): - ret_val["is_highest"] = True - return ret_val - - -class BaseFooterHTML(CDNCacheTagsMixin, APIView): - - """ - Render and return footer markup. - - Query parameters: - - - project - - version - - page: Sphinx's page name (name of the source file), - used to build the "edit on" links. - - theme: Used to decide how to integrate the flyout menu. - - docroot: Path where all the source documents are. - Used to build the ``edit_on`` URL. - - source_suffix: Suffix from the source document. - Used to build the ``edit_on`` URL. - - .. note:: - - The methods `_get_project` and `_get_version` - are called many times, so a basic cache is implemented. - """ - - http_method_names = ["get"] - permission_classes = [IsAuthorizedToViewVersion] - renderer_classes = [JSONRenderer, JSONPRenderer] - project_cache_tag = "rtd-footer" - - @lru_cache(maxsize=1) - def _get_project(self): - project_slug = self.request.GET.get("project", None) - project = get_object_or_404(Project, slug=project_slug) - return project - - @lru_cache(maxsize=1) - def _get_version(self): - version_slug = self.request.GET.get("version", None) - - # Hack in a fix for missing version slug deploy - # that went out a while back - if version_slug == "": - version_slug = LATEST - - project = self._get_project() - version = get_object_or_404( - project.versions.all(), - slug__iexact=version_slug, - ) - return version - - def _get_active_versions_sorted(self): - """Get all versions that the user has access, sorted.""" - project = self._get_project() - versions = project.ordered_active_versions( - user=self.request.user, - include_hidden=False, - ) - return versions - - def _get_context(self): - theme = self.request.GET.get("theme", False) - docroot = self.request.GET.get("docroot", "") - source_suffix = self.request.GET.get("source_suffix", ".rst") - - new_theme = theme == "sphinx_rtd_theme" - - project = self._get_project() - main_project = project.main_language_project or project - version = self._get_version() - - page_slug = self.request.GET.get("page", "") - path = "" - if page_slug and page_slug != "index": - if version.documentation_type in {SPHINX_HTMLDIR, MKDOCS}: - path = re.sub("/index$", "", page_slug) + "/" - else: - path = page_slug + ".html" - - context = { - "project": project, - "version": version, - "path": path, - "downloads": version.get_downloads(pretty=True), - "current_version": version, - "versions": self._get_active_versions_sorted(), - "main_project": main_project, - "translations": main_project.translations.all(), - "current_language": project.language, - "new_theme": new_theme, - "settings": settings, - "github_edit_url": version.get_github_url( - docroot, - page_slug, - source_suffix, - "edit", - ), - "github_view_url": version.get_github_url( - docroot, - page_slug, - source_suffix, - "view", - ), - "gitlab_edit_url": version.get_gitlab_url( - docroot, - page_slug, - source_suffix, - "edit", - ), - "gitlab_view_url": version.get_gitlab_url( - docroot, - page_slug, - source_suffix, - "view", - ), - "bitbucket_url": version.get_bitbucket_url( - docroot, - page_slug, - source_suffix, - ), - } - return context - - def get(self, request, format=None): - project = self._get_project() - version = self._get_version() - version_compare_data = get_version_compare_data( - project, - base_version=version, - user=request.user, - ) - - context = self._get_context() - html = template_loader.get_template("restapi/footer.html").render( - context, - request, - ) - - show_version_warning = project.show_version_warning and not version.is_external - - resp_data = { - "html": html, - "show_version_warning": show_version_warning, - "version_active": version.active, - "version_compare": version_compare_data, - "version_supported": version.supported, - } - - return Response(resp_data) - - -class FooterHTML(SettingsOverrideObject): - _default_class = BaseFooterHTML diff --git a/readthedocs/api/v2/views/proxied.py b/readthedocs/api/v2/views/proxied.py index 729b3370422..80a1de43f3e 100644 --- a/readthedocs/api/v2/views/proxied.py +++ b/readthedocs/api/v2/views/proxied.py @@ -1,17 +1,8 @@ -from readthedocs.api.v2.views.footer_views import BaseFooterHTML from readthedocs.core.mixins import ProxiedAPIMixin from readthedocs.core.utils.extend import SettingsOverrideObject from readthedocs.embed.views import EmbedAPI -class BaseProxiedFooterHTML(ProxiedAPIMixin, BaseFooterHTML): - pass - - -class ProxiedFooterHTML(SettingsOverrideObject): - _default_class = BaseProxiedFooterHTML - - class ProxiedEmbedAPIBase(ProxiedAPIMixin, EmbedAPI): pass diff --git a/readthedocs/core/static-src/core/js/doc-embed/footer.js b/readthedocs/core/static-src/core/js/doc-embed/footer.js deleted file mode 100644 index 3f1beedc0a9..00000000000 --- a/readthedocs/core/static-src/core/js/doc-embed/footer.js +++ /dev/null @@ -1,103 +0,0 @@ -var rtddata = require('./rtd-data'); -var versionCompare = require('./version-compare'); - -var EXPLICIT_FLYOUT_PLACEMENT_SELECTOR = '#readthedocs-embed-flyout'; - - -function injectFooter(data) { - // Injects the footer into the page - // There are 3 main cases: - // * EXPLICIT_FLYOUT_PLACEMENT_SELECTOR is defined, inject it there - // * The page looks like our Sphinx theme, updated the existing div - // * All other pages just get it appended to the - - var config = rtddata.get(); - let placement = document.querySelector(EXPLICIT_FLYOUT_PLACEMENT_SELECTOR); - if (placement !== null) { - placement.innerHTML = data['html']; - } - else if (config.is_sphinx_builder() && config.is_rtd_like_theme()) { - let placement = document.querySelector('div.rst-other-versions'); - if (placement !== null) { - placement.innerHTML = data['html']; - } - } else { - document.body.insertAdjacentHTML('beforeend', data['html']); - } - - if (!data['version_active']) { - for (let element of document.getElementsByClassName('rst-current-version')) { - element.classList.add('rst-out-of-date'); - } - } else if (!data['version_supported']) { - //$('.rst-current-version').addClass('rst-active-old-version') - } -} - - -function init() { - var rtd = rtddata.get(); - - var get_data = { - project: rtd['project'], - version: rtd['version'], - // Page is a sphinx concept only, - // avoid serializing this as a literal `null` instead of empty. - page: rtd['page'] || "", - theme: rtd.get_theme_name(), - }; - - // Crappy heuristic, but people change the theme name on us. - // So we have to do some duck typing. - if ("docroot" in rtd) { - get_data['docroot'] = rtd['docroot']; - } - - if ("source_suffix" in rtd) { - get_data['source_suffix'] = rtd['source_suffix']; - } - - if (window.location.pathname.indexOf('/projects/') === 0) { - get_data['subproject'] = true; - } - - // Get footer HTML from API and inject it into the page. - let footer_api_url = rtd.proxied_api_host + "/api/v2/footer_html/?" + new URLSearchParams(get_data).toString(); - fetch(footer_api_url, {method: 'GET'}) - .then(response => { - if (!response.ok) { - throw new Error(); - } - return response.json(); - }) - .then(data => { - if (data.show_version_warning) { - versionCompare.init(data.version_compare); - } - injectFooter(data); - }) - .catch(error => { - console.error('Error loading Read the Docs footer'); - }); - - // Register page view. - let data = { - project: rtd['project'], - version: rtd['version'], - absolute_uri: window.location.href, - }; - let url = rtd.proxied_api_host + '/api/v2/analytics/?' + new URLSearchParams(data).toString(); - fetch(url, {method: 'GET', cache: 'no-store'}) - .then(response => { - if (!response.ok) { - throw new Error(); - } - }) - .catch(error => { - console.error('Error registering page view'); - }); -} - -module.exports = { - init: init -}; diff --git a/readthedocs/core/static-src/core/js/doc-embed/sponsorship.js b/readthedocs/core/static-src/core/js/doc-embed/sponsorship.js deleted file mode 100644 index 186a9b33b4c..00000000000 --- a/readthedocs/core/static-src/core/js/doc-embed/sponsorship.js +++ /dev/null @@ -1,221 +0,0 @@ -/* Read the Docs - Documentation promotions */ - -var constants = require('./constants'); -var rtddata = require('./rtd-data'); - -var rtd; - -var EXPLICIT_PLACEMENT_SELECTOR = "[data-ea-publisher]"; - -// Old way to control the exact placement of an ad -var OLD_EXPLICIT_PLACEMENT_SELECTOR = '#ethical-ad-placement'; - - -/* - * Inject the EthicalAds ad client - */ -function inject_ads_client() { - var script = document.createElement("script"); - script.src = "https://media.ethicalads.io/media/client/beta/ethicalads.min.js"; - script.type = "text/javascript"; - script.async = true; - script.id = "ethicaladsjs"; - document.getElementsByTagName("head")[0].appendChild(script); -} - -/* - * Creates a div where the ad could go - */ -function create_ad_placement() { - var selector = null; - var class_name; // Used for theme specific CSS customizations - var style_name; - var ad_type = "readthedocs-sidebar"; - var element; - var offset; - - if ($(EXPLICIT_PLACEMENT_SELECTOR).length > 0) { - $(EXPLICIT_PLACEMENT_SELECTOR).attr("data-ea-publisher", "readthedocs"); - $(EXPLICIT_PLACEMENT_SELECTOR).attr("data-ea-manual", "true"); - if ($(EXPLICIT_PLACEMENT_SELECTOR).attr("data-ea-type") !== "image" && $(EXPLICIT_PLACEMENT_SELECTOR).attr("data-ea-type") !== "text") { - $(EXPLICIT_PLACEMENT_SELECTOR).attr("data-ea-type", "readthedocs-sidebar"); - } - return $(EXPLICIT_PLACEMENT_SELECTOR); - } else if ($(OLD_EXPLICIT_PLACEMENT_SELECTOR).length > 0) { - selector = OLD_EXPLICIT_PLACEMENT_SELECTOR; - if (rtd.is_rtd_like_theme()) { - class_name = 'ethical-rtd ethical-dark-theme'; - } else { - class_name = 'ethical-alabaster'; - } - } else if (rtd.is_mkdocs_builder() && rtd.is_rtd_like_theme()) { - selector = 'nav.wy-nav-side'; - class_name = 'ethical-rtd ethical-dark-theme'; - } else if (rtd.is_rtd_like_theme()) { - selector = 'nav.wy-nav-side > div.wy-side-scroll'; - class_name = 'ethical-rtd ethical-dark-theme'; - } else if (rtd.is_alabaster_like_theme()) { - selector = 'div.sphinxsidebar > div.sphinxsidebarwrapper'; - class_name = 'ethical-alabaster'; - } - - if (selector) { - // Determine if this element would be above the fold - // If this is off screen, instead create an ad in the footer - // Assumes the ad would be ~200px high - element = $("
").appendTo(selector); - offset = element.offset(); - if (!offset || (offset.top - window.scrollY + 200) > window.innerHeight) { - if (rtd.is_rtd_like_theme()) { - selector = $('
').insertAfter('footer hr'); - class_name = 'ethical-rtd'; - - // Use the stickybox placement 100% of the time, - // now that we're happy with its performance. - style_name = 'stickybox'; - ad_type = 'image'; - } else if (rtd.is_alabaster_like_theme()) { - selector = 'div.bodywrapper .body'; - class_name = 'ethical-alabaster'; - } - } - element.remove(); - - // Add the element where the ad will go - return $('
') - .attr("id", "rtd-sidebar") - .attr("data-ea-publisher", "readthedocs") - .attr("data-ea-type", ad_type) - .attr("data-ea-manual", "true") - .attr("data-ea-style", style_name) - .addClass(class_name) - .appendTo(selector); - } - - return null; -} - -function detect_adblock() { - // Status codes are not correctly reported on JSONP requests - // So we resort to different ways to detect adblockers - var detected = false; - - // Check if our ad element is blocked - $('
') - .attr('id', 'rtd-detection') - .attr('class', 'ethical-rtd') - .html(' ') - .appendTo('body'); - if ($('#rtd-detection').height() === 0) { - detected = true; - } - - // Remove the test element regardless - $('#rtd-detection').remove(); - - return detected; -} - -function adblock_admonition() { - console.log('---------------------------------------------------------------------------------------'); - console.log('Read the Docs hosts documentation for tens of thousands of open source projects.'); - console.log('We fund our development (we are open source) and operations through advertising.'); - - console.log('We promise to:'); - console.log(' - never let advertisers run 3rd party JavaScript'); - console.log(' - never sell user data to advertisers or other 3rd parties'); - console.log(' - only show advertisements of interest to developers'); - console.log('Read more about our approach to advertising here: https://docs.readthedocs.io/en/latest/advertising/ethical-advertising.html'); - console.log('%cPlease allow our Ethical Ads or go ad-free:', 'font-size: 2em'); - console.log('https://docs.readthedocs.io/en/latest/advertising/ad-blocking.html'); - console.log('--------------------------------------------------------------------------------------'); -} - -function adblock_nag(placement) { - // Place an ad block nag into the sidebar - var unblock_url = 'https://docs.readthedocs.io/en/latest/advertising/ad-blocking.html#allowing-ethical-ads'; - var ad_free_url = 'https://readthedocs.org/sustainability/'; - var container = null; - - if (placement) { - container = placement.attr('class', 'keep-us-sustainable'); - - $('

').text('Support Read the Docs!').appendTo(container); - $('

').html('Please help keep us sustainable by allowing our Ethical Ads in your ad blocker or go ad-free by subscribing.').appendTo(container); - $('

').text('Thank you! \u2764\ufe0f').appendTo(container); - } -} - -function init() { - var placement; - - rtd = rtddata.get(); - - if (!rtd.show_promo()) { - return; - } - - placement = create_ad_placement(); - - // Inject ads - inject_ads_client(); - - $.ajax({ - url: rtd.api_host + "/api/v2/sustainability/data/", - crossDomain: true, - xhrFields: { - withCredentials: true, - }, - dataType: "jsonp", - data: { - format: "jsonp", - project: rtd.project, - }, - success: function (data) { - if (!placement || data.ad_free) { - // No valid placement or project/user is ad free - return; - } - - // Set the keyword, campaign data, and publisher - if (data.keywords) { - placement.attr("data-ea-keywords", data.keywords.join("|")); - } - if (data.campaign_types) { - placement.attr("data-ea-campaign-types", data.campaign_types.join("|")); - } - if (data.publisher) { - placement.attr("data-ea-publisher", data.publisher); - } - - if (typeof ethicalads !== "undefined") { - // Trigger ad request - ethicalads.load(); - } else if (!rtd.ad_free && detect_adblock()) { - // Ad client prevented from loading - check ad blockers - adblock_admonition(); - adblock_nag(placement); - } else { - // The ad client hasn't loaded yet which could happen due to a variety of issues - // Add an event listener for it to load - document.getElementById("ethicaladsjs").addEventListener("load", function () { - if (typeof ethicalads !== "undefined") { - ethicalads.load(); - } - }); - } - }, - error: function () { - console.error('Error loading Read the Docs user and project information'); - - if (!rtd.ad_free && detect_adblock()) { - adblock_admonition(); - adblock_nag(placement); - } - }, - }); -} - -module.exports = { - init: init, -}; diff --git a/readthedocs/core/static-src/core/js/readthedocs-doc-embed.js b/readthedocs/core/static-src/core/js/readthedocs-doc-embed.js index 81e6521b138..8aab0ac9bcb 100644 --- a/readthedocs/core/static-src/core/js/readthedocs-doc-embed.js +++ b/readthedocs/core/static-src/core/js/readthedocs-doc-embed.js @@ -1,5 +1,3 @@ -const sponsorship = require('./doc-embed/sponsorship'); -const footer = require('./doc-embed/footer.js'); // grokthedocs = require('./doc-embed/grokthedocs-client'), // mkdocs = require('./doc-embed/mkdocs'), const sphinx = require('./doc-embed/sphinx'); @@ -39,10 +37,8 @@ function injectJQuery(init) { domReady(function () { // Block on jQuery loading before we run any of our code. injectJQuery(function () { - footer.init(); sphinx.init(); search.init(); - sponsorship.init(); }); }); }()); diff --git a/readthedocs/core/static/core/js/readthedocs-doc-embed.js b/readthedocs/core/static/core/js/readthedocs-doc-embed.js index 5792ef07f0a..02aa05f357f 100644 --- a/readthedocs/core/static/core/js/readthedocs-doc-embed.js +++ b/readthedocs/core/static/core/js/readthedocs-doc-embed.js @@ -1 +1 @@ -!function i(o,a,r){function s(t,e){if(!a[t]){if(!o[t]){var n="function"==typeof require&&require;if(!e&&n)return n(t,!0);if(d)return d(t,!0);throw(e=new Error("Cannot find module '"+t+"'")).code="MODULE_NOT_FOUND",e}n=a[t]={exports:{}},o[t][0].call(n.exports,function(e){return s(o[t][1][e]||e)},n,n.exports,i,o,a,r)}return a[t].exports}for(var d="function"==typeof require&&require,e=0;e

"),n("table.docutils.footnote").wrap("
"),n("table.docutils.citation").wrap("
"),n(".wy-menu-vertical ul").not(".simple").siblings("a").each(function(){var t=n(this);(expand=n('')).on("click",function(e){return i.toggleCurrent(t),e.stopPropagation(),!1}),t.prepend(expand)})},reset:function(){var e=encodeURI(window.location.hash)||"#";try{var t,n=$(".wy-menu-vertical"),i=n.find('[href="'+e+'"]');0===i.length&&(t=$('.document [id="'+e.substring(1)+'"]').closest("div.section"),0===(i=n.find('[href="#'+t.attr("id")+'"]')).length&&(i=n.find('[href="#"]'))),0this.docHeight||(this.navBar.scrollTop(n),this.winPosition=e)},onResize:function(){this.winResize=!1,this.winHeight=this.win.height(),this.docHeight=$(document).height()},hashChange:function(){this.linkScroll=!0,this.win.one("hashchange",function(){this.linkScroll=!1})},toggleCurrent:function(e){e=e.closest("li");e.siblings("li.current").removeClass("current"),e.siblings().find("li.current").removeClass("current"),e.find("> ul li.current").removeClass("current"),e.toggleClass("current")}},"undefined"!=typeof window&&(window.SphinxRtdTheme={Navigation:t.exports.ThemeNav,StickyNav:t.exports.ThemeNav});for(var a=0,o=["ms","moz","webkit","o"],r=0;r{if(e.ok)return e.json();throw new Error}).then(t=>{t.show_version_warning&&a.init(t.version_compare);{var n=o.get();let e=document.querySelector(r);if(null!==e)e.innerHTML=t.html;else if(n.is_sphinx_builder()&&n.is_rtd_like_theme()){let e=document.querySelector("div.rst-other-versions");null!==e&&(e.innerHTML=t.html)}else document.body.insertAdjacentHTML("beforeend",t.html);if(t.version_active)t.version_supported;else for(var i of document.getElementsByClassName("rst-current-version"))i.classList.add("rst-out-of-date");return}}).catch(e=>{console.error("Error loading Read the Docs footer")}),{project:t.project,version:t.version,absolute_uri:window.location.href}),t=t.proxied_api_host+"/api/v2/analytics/?"+new URLSearchParams(e).toString();fetch(t,{method:"GET",cache:"no-store"}).then(e=>{if(!e.ok)throw new Error}).catch(e=>{console.error("Error registering page view")})}}},{"./rtd-data":4,"./version-compare":9}],4:[function(e,t,n){var i=e("./constants"),o={is_rtd_like_theme:function(){return 1===document.querySelectorAll("div.rst-other-versions").length||(this.theme===i.THEME_RTD||this.theme===i.THEME_MKDOCS_RTD)},is_alabaster_like_theme:function(){return-1{t.appendChild(e)})}for(c of t.getElementsByTagName("span"))c.className="highlighted";n.appendChild(t),l{if(e.ok)return e.json();throw new Error}).then(e=>{0{i()})}var t,E=o.project,a=o.version,r=o.language||"en";"undefined"!=typeof Search&&E&&a&&(o.features&&o.features.docsearch_disabled?console.log("Server side search is disabled."):(t=Search.query,Search.query_fallback=t,Search.query=e)),s(function(){"undefined"!=typeof Search&&Search.init()})}t.exports={init:function(){var e=i.get();e.is_sphinx_builder()?a(e):console.log("Server side search is disabled.")}}},{"./rtd-data":4,"./utils":8}],6:[function(i,e,t){const o=i("./rtd-data"),a=i("./utils")["domReady"];e.exports={init:function(){var e,t=o.get(),n=document.querySelector("[data-toggle='rst-current-version']");null!=n&&n.addEventListener("click",function(){var e=$("[data-toggle='rst-versions']").hasClass("shift-up")?"was_open":"was_closed";"undefined"!=typeof READTHEDOCS_DATA&&READTHEDOCS_DATA.global_analytics_code&&("undefined"!=typeof gtag?gtag("event","Click",{event_category:"Flyout",event_label:e,send_to:"rtfd"}):"undefined"!=typeof ga?ga("rtfd.send","event","Flyout","Click",e):"undefined"!=typeof _gaq&&_gaq.push(["rtfd._setAccount","UA-17997319-1"],["rtfd._trackEvent","Flyout","Click",e]))}),void 0===window.SphinxRtdTheme&&(e=i("./../../../../../../bower_components/sphinx-rtd-theme/js/theme.js").ThemeNav,a(function(){setTimeout(function(){e.navBar||e.enable()},1e3)}),t.is_rtd_like_theme()&&!$("div.wy-side-scroll:first").length&&(console.log("Applying theme sidebar fix..."),n=$("nav.wy-nav-side:first"),t=$("
").addClass("wy-side-scroll"),n.children().detach().appendTo(t),t.prependTo(n),e.navBar=t))}}},{"./../../../../../../bower_components/sphinx-rtd-theme/js/theme.js":1,"./rtd-data":4,"./utils":8}],7:[function(e,t,n){e("./constants");var s,d=e("./rtd-data"),l="[data-ea-publisher]",c="#ethical-ad-placement";function h(){var e=!1;return $("
").attr("id","rtd-detection").attr("class","ethical-rtd").html(" ").appendTo("body"),0===$("#rtd-detection").height()&&(e=!0),$("#rtd-detection").remove(),e}function u(){console.log("---------------------------------------------------------------------------------------"),console.log("Read the Docs hosts documentation for tens of thousands of open source projects."),console.log("We fund our development (we are open source) and operations through advertising."),console.log("We promise to:"),console.log(" - never let advertisers run 3rd party JavaScript"),console.log(" - never sell user data to advertisers or other 3rd parties"),console.log(" - only show advertisements of interest to developers"),console.log("Read more about our approach to advertising here: https://docs.readthedocs.io/en/latest/advertising/ethical-advertising.html"),console.log("%cPlease allow our Ethical Ads or go ad-free:","font-size: 2em"),console.log("https://docs.readthedocs.io/en/latest/advertising/ad-blocking.html"),console.log("--------------------------------------------------------------------------------------")}function p(e){e&&(e=e.attr("class","keep-us-sustainable"),$("

").text("Support Read the Docs!").appendTo(e),$("

").html('Please help keep us sustainable by allowing our Ethical Ads in your ad blocker or go ad-free by subscribing.').appendTo(e),$("

").text("Thank you! ❤️").appendTo(e))}t.exports={init:function(){var t,e,n,i,o,a,r;(s=d.get()).show_promo()&&(o=null,a="readthedocs-sidebar",t=0<$(l).length?($(l).attr("data-ea-publisher","readthedocs"),$(l).attr("data-ea-manual","true"),"image"!==$(l).attr("data-ea-type")&&"text"!==$(l).attr("data-ea-type")&&$(l).attr("data-ea-type","readthedocs-sidebar"),$(l)):(0<$(c).length?(o=c,e=s.is_rtd_like_theme()?"ethical-rtd ethical-dark-theme":"ethical-alabaster"):s.is_mkdocs_builder()&&s.is_rtd_like_theme()?(o="nav.wy-nav-side",e="ethical-rtd ethical-dark-theme"):s.is_rtd_like_theme()?(o="nav.wy-nav-side > div.wy-side-scroll",e="ethical-rtd ethical-dark-theme"):s.is_alabaster_like_theme()&&(o="div.sphinxsidebar > div.sphinxsidebarwrapper",e="ethical-alabaster"),o?((!(r=(i=$("

").appendTo(o)).offset())||r.top-window.scrollY+200>window.innerHeight)&&(s.is_rtd_like_theme()?(o=$("
").insertAfter("footer hr"),e="ethical-rtd",n="stickybox",a="image"):s.is_alabaster_like_theme()&&(o="div.bodywrapper .body",e="ethical-alabaster")),i.remove(),$("
").attr("id","rtd-sidebar").attr("data-ea-publisher","readthedocs").attr("data-ea-type",a).attr("data-ea-manual","true").attr("data-ea-style",n).addClass(e).appendTo(o)):null),(r=document.createElement("script")).src="https://media.ethicalads.io/media/client/beta/ethicalads.min.js",r.type="text/javascript",r.async=!0,r.id="ethicaladsjs",document.getElementsByTagName("head")[0].appendChild(r),$.ajax({url:s.api_host+"/api/v2/sustainability/data/",crossDomain:!0,xhrFields:{withCredentials:!0},dataType:"jsonp",data:{format:"jsonp",project:s.project},success:function(e){t&&!e.ad_free&&(e.keywords&&t.attr("data-ea-keywords",e.keywords.join("|")),e.campaign_types&&t.attr("data-ea-campaign-types",e.campaign_types.join("|")),e.publisher&&t.attr("data-ea-publisher",e.publisher),"undefined"!=typeof ethicalads?ethicalads.load():!s.ad_free&&h()?(u(),p(t)):document.getElementById("ethicaladsjs").addEventListener("load",function(){"undefined"!=typeof ethicalads&ðicalads.load()}))},error:function(){console.error("Error loading Read the Docs user and project information"),!s.ad_free&&h()&&(u(),p(t))}}))}}},{"./constants":2,"./rtd-data":4}],8:[function(e,t,n){t.exports={createDomNode:function(e,t){let n=document.createElement(e);if(t)for(var i of Object.keys(t))n.setAttribute(i,t[i]);return n},domReady:function(e){"complete"===document.readyState||"interactive"===document.readyState?setTimeout(e,1):document.addEventListener("DOMContentLoaded",e)}}},{}],9:[function(e,t,n){const a=e("./rtd-data"),r=e("./utils")["createDomNode"];t.exports={init:function(n){var i,o=a.get();if(!n.is_highest){o=window.location.pathname.replace(o.version,n.slug);let t=r("div",{class:"admonition warning"}),e=r("a",{href:o});e.innerText=n.slug,t.innerHTML='

Note

You are not reading the most recent version of this documentation. '+e.outerHTML+" is the latest version available.

";for(i of["[role=main]","main","div.body","div.document"]){let e=document.querySelector(i);if(null!==e){e.prepend(t);break}}}}}},{"./rtd-data":4,"./utils":8}],10:[function(i,e,t){const o=i("./doc-embed/sponsorship"),a=i("./doc-embed/footer.js"),r=i("./doc-embed/sphinx"),s=i("./doc-embed/search"),n=i("./doc-embed/utils")["domReady"],d=i("./doc-embed/rtd-data");n(function(){var t=function(){a.init(),r.init(),s.init(),o.init()};if(window.jQuery)t();else{console.debug("JQuery not found. Injecting.");var n=d.get();let e=document.createElement("script");e.type="text/javascript",e.src=n.proxied_static_path+"vendor/jquery.js",e.onload=function(){window.$=i("jquery"),window.jQuery=window.$,t()},document.head.appendChild(e)}})},{"./doc-embed/footer.js":3,"./doc-embed/rtd-data":4,"./doc-embed/search":5,"./doc-embed/sphinx":6,"./doc-embed/sponsorship":7,"./doc-embed/utils":8,jquery:"jquery"}]},{},[10]); \ No newline at end of file +!function i(o,r,a){function s(t,e){if(!r[t]){if(!o[t]){var n="function"==typeof require&&require;if(!e&&n)return n(t,!0);if(c)return c(t,!0);throw(e=new Error("Cannot find module '"+t+"'")).code="MODULE_NOT_FOUND",e}n=r[t]={exports:{}},o[t][0].call(n.exports,function(e){return s(o[t][1][e]||e)},n,n.exports,i,o,r,a)}return r[t].exports}for(var c="function"==typeof require&&require,e=0;e
"),n("table.docutils.footnote").wrap("
"),n("table.docutils.citation").wrap("
"),n(".wy-menu-vertical ul").not(".simple").siblings("a").each(function(){var t=n(this);(expand=n('')).on("click",function(e){return i.toggleCurrent(t),e.stopPropagation(),!1}),t.prepend(expand)})},reset:function(){var e=encodeURI(window.location.hash)||"#";try{var t,n=$(".wy-menu-vertical"),i=n.find('[href="'+e+'"]');0===i.length&&(t=$('.document [id="'+e.substring(1)+'"]').closest("div.section"),0===(i=n.find('[href="#'+t.attr("id")+'"]')).length&&(i=n.find('[href="#"]'))),0this.docHeight||(this.navBar.scrollTop(n),this.winPosition=e)},onResize:function(){this.winResize=!1,this.winHeight=this.win.height(),this.docHeight=$(document).height()},hashChange:function(){this.linkScroll=!0,this.win.one("hashchange",function(){this.linkScroll=!1})},toggleCurrent:function(e){e=e.closest("li");e.siblings("li.current").removeClass("current"),e.siblings().find("li.current").removeClass("current"),e.find("> ul li.current").removeClass("current"),e.toggleClass("current")}},"undefined"!=typeof window&&(window.SphinxRtdTheme={Navigation:t.exports.ThemeNav,StickyNav:t.exports.ThemeNav});for(var r=0,o=["ms","moz","webkit","o"],a=0;a{t.appendChild(e)})}for(d of t.getElementsByTagName("span"))d.className="highlighted";n.appendChild(t),l{if(e.ok)return e.json();throw new Error}).then(e=>{0{i()})}var t,w=o.project,r=o.version,a=o.language||"en";"undefined"!=typeof Search&&w&&r&&(o.features&&o.features.docsearch_disabled?console.log("Server side search is disabled."):(t=Search.query,Search.query_fallback=t,Search.query=e)),s(function(){"undefined"!=typeof Search&&Search.init()})}t.exports={init:function(){var e=i.get();e.is_sphinx_builder()?r(e):console.log("Server side search is disabled.")}}},{"./rtd-data":3,"./utils":6}],5:[function(i,e,t){const o=i("./rtd-data"),r=i("./utils")["domReady"];e.exports={init:function(){var e,t=o.get(),n=document.querySelector("[data-toggle='rst-current-version']");null!=n&&n.addEventListener("click",function(){var e=$("[data-toggle='rst-versions']").hasClass("shift-up")?"was_open":"was_closed";"undefined"!=typeof READTHEDOCS_DATA&&READTHEDOCS_DATA.global_analytics_code&&("undefined"!=typeof gtag?gtag("event","Click",{event_category:"Flyout",event_label:e,send_to:"rtfd"}):"undefined"!=typeof ga?ga("rtfd.send","event","Flyout","Click",e):"undefined"!=typeof _gaq&&_gaq.push(["rtfd._setAccount","UA-17997319-1"],["rtfd._trackEvent","Flyout","Click",e]))}),void 0===window.SphinxRtdTheme&&(e=i("./../../../../../../bower_components/sphinx-rtd-theme/js/theme.js").ThemeNav,r(function(){setTimeout(function(){e.navBar||e.enable()},1e3)}),t.is_rtd_like_theme()&&!$("div.wy-side-scroll:first").length&&(console.log("Applying theme sidebar fix..."),n=$("nav.wy-nav-side:first"),t=$("
").addClass("wy-side-scroll"),n.children().detach().appendTo(t),t.prependTo(n),e.navBar=t))}}},{"./../../../../../../bower_components/sphinx-rtd-theme/js/theme.js":1,"./rtd-data":3,"./utils":6}],6:[function(e,t,n){t.exports={createDomNode:function(e,t){let n=document.createElement(e);if(t)for(var i of Object.keys(t))n.setAttribute(i,t[i]);return n},domReady:function(e){"complete"===document.readyState||"interactive"===document.readyState?setTimeout(e,1):document.addEventListener("DOMContentLoaded",e)}}},{}],7:[function(i,e,t){const o=i("./doc-embed/sphinx"),r=i("./doc-embed/search"),n=i("./doc-embed/utils")["domReady"],a=i("./doc-embed/rtd-data");n(function(){var t=function(){o.init(),r.init()};if(window.jQuery)t();else{console.debug("JQuery not found. Injecting.");var n=a.get();let e=document.createElement("script");e.type="text/javascript",e.src=n.proxied_static_path+"vendor/jquery.js",e.onload=function(){window.$=i("jquery"),window.jQuery=window.$,t()},document.head.appendChild(e)}})},{"./doc-embed/rtd-data":3,"./doc-embed/search":4,"./doc-embed/sphinx":5,"./doc-embed/utils":6,jquery:"jquery"}]},{},[7]); \ No newline at end of file diff --git a/readthedocs/proxito/middleware.py b/readthedocs/proxito/middleware.py index 772ce06a781..cd6f14c9a29 100644 --- a/readthedocs/proxito/middleware.py +++ b/readthedocs/proxito/middleware.py @@ -48,7 +48,6 @@ class ProxitoMiddleware(MiddlewareMixin): # which depends on the proxito middleware. skip_views = ( "health_check", - "footer_html", "search_api", "embed_api", ) diff --git a/readthedocs/proxito/tests/test_headers.py b/readthedocs/proxito/tests/test_headers.py index 8a4c199721b..f8919648503 100644 --- a/readthedocs/proxito/tests/test_headers.py +++ b/readthedocs/proxito/tests/test_headers.py @@ -5,10 +5,9 @@ ACCESS_CONTROL_ALLOW_ORIGIN, ) from django.test import override_settings -from django.urls import reverse from django_dynamic_fixture import get -from readthedocs.builds.constants import EXTERNAL, LATEST +from readthedocs.builds.constants import EXTERNAL from readthedocs.builds.models import Version from readthedocs.organizations.models import Organization from readthedocs.projects.constants import PRIVATE, PUBLIC @@ -153,16 +152,6 @@ def test_custom_domain_headers(self): r["X-RTD-Path"], "/proxito/media/html/project/latest/index.html" ) - def test_footer_headers(self): - version = self.project.versions.get(slug=LATEST) - url = ( - reverse("footer_html") - + f"?project={self.project.slug}&version={version.slug}" - ) - r = self.client.get(url, headers={"host": "project.dev.readthedocs.io"}) - self.assertEqual(r.status_code, 200) - self.assertEqual(r["Cache-Tag"], "project,project:latest,project:rtd-footer") - def test_user_domain_headers(self): hostname = "docs.domain.com" self.domain = fixture.get( diff --git a/readthedocs/proxito/tests/test_proxied_api.py b/readthedocs/proxito/tests/test_proxied_api.py deleted file mode 100644 index 72eca03245d..00000000000 --- a/readthedocs/proxito/tests/test_proxied_api.py +++ /dev/null @@ -1,14 +0,0 @@ -from django.test import TestCase, override_settings - -from readthedocs.rtd_tests.tests.test_footer import BaseTestFooterHTML - - -@override_settings(PUBLIC_DOMAIN="readthedocs.io") -class TestProxiedFooterHTML(BaseTestFooterHTML, TestCase): - def setUp(self): - super().setUp() - self.host = "pip.readthedocs.io" - - def render(self): - r = self.client.get(self.url, headers={"host": self.host}) - return r diff --git a/readthedocs/rtd_tests/tests/test_api_version_compare.py b/readthedocs/rtd_tests/tests/test_api_version_compare.py deleted file mode 100644 index bec4dc5ef65..00000000000 --- a/readthedocs/rtd_tests/tests/test_api_version_compare.py +++ /dev/null @@ -1,36 +0,0 @@ -from django.test import TestCase - -from readthedocs.api.v2.views.footer_views import get_version_compare_data -from readthedocs.builds.constants import LATEST -from readthedocs.projects.models import Project - - -class VersionCompareTests(TestCase): - fixtures = ["eric.json", "test_data.json"] - - def setUp(self): - Project.objects.update(show_version_warning=True) - - def test_not_highest(self): - project = Project.objects.get(slug="read-the-docs") - version = project.versions.get(slug="0.2.1") - - data = get_version_compare_data(project, version) - self.assertEqual(data["is_highest"], False) - - def test_latest_version_highest(self): - project = Project.objects.get(slug="read-the-docs") - - data = get_version_compare_data(project) - self.assertEqual(data["is_highest"], True) - - version = project.versions.get(slug=LATEST) - data = get_version_compare_data(project, version) - self.assertEqual(data["is_highest"], True) - - def test_real_highest(self): - project = Project.objects.get(slug="read-the-docs") - version = project.versions.get(slug="0.2.2") - - data = get_version_compare_data(project, version) - self.assertEqual(data["is_highest"], True) diff --git a/readthedocs/rtd_tests/tests/test_footer.py b/readthedocs/rtd_tests/tests/test_footer.py deleted file mode 100644 index beb1f5f9bc6..00000000000 --- a/readthedocs/rtd_tests/tests/test_footer.py +++ /dev/null @@ -1,530 +0,0 @@ -from unittest import mock - -import pytest -from django.contrib.auth.models import User -from django.test import TestCase, override_settings -from django.urls import reverse -from django_dynamic_fixture import get -from rest_framework.test import APIRequestFactory - -from readthedocs.api.v2.views.footer_views import get_version_compare_data -from readthedocs.builds.constants import BRANCH, EXTERNAL, LATEST, TAG -from readthedocs.builds.models import Version -from readthedocs.organizations.models import Organization -from readthedocs.projects.constants import GITHUB_BRAND, GITLAB_BRAND, PRIVATE, PUBLIC -from readthedocs.projects.models import Project -from readthedocs.subscriptions.constants import TYPE_CNAME -from readthedocs.subscriptions.products import RTDProductFeature - - -class BaseTestFooterHTML: - def setUp(self): - self.pip = get( - Project, - slug="pip", - repo="https://github.com/rtfd/readthedocs.org", - privacy_level=PUBLIC, - external_builds_privacy_level=PUBLIC, - main_language_project=None, - ) - self.pip.versions.update(privacy_level=PUBLIC, built=True) - - self.latest = self.pip.versions.get(slug=LATEST) - self.url = ( - reverse("footer_html") - + f"?project={self.pip.slug}&version={self.latest.slug}&page=index&docroot=/" - ) - - self.factory = APIRequestFactory() - - def render(self): - r = self.client.get(self.url) - return r - - def test_footer(self): - pip = Project.objects.get(slug="pip") - pip.show_version_warning = True - pip.save() - - r = self.render() - self.assertTrue(r.data["version_active"]) - self.assertTrue(r.data["version_compare"]["is_highest"]) - self.assertTrue(r.data["version_supported"]) - self.assertTrue(r.data["show_version_warning"]) - self.assertEqual(r.context["main_project"], self.pip) - self.assertEqual(r.status_code, 200) - - self.latest.active = False - self.latest.save() - r = self.render() - self.assertFalse(r.data["version_active"]) - self.assertEqual(r.status_code, 200) - - def test_footer_dont_show_version_warning(self): - pip = Project.objects.get(slug="pip") - pip.show_version_warning = False - pip.save() - - r = self.render() - self.assertEqual(r.status_code, 200) - self.assertTrue(r.data["version_active"]) - self.assertFalse(r.data["version_compare"]["is_highest"]) - self.assertTrue(r.data["version_supported"]) - self.assertFalse(r.data["show_version_warning"]) - self.assertEqual(r.context["main_project"], self.pip) - - def test_footer_show_explicit_name_for_external_version(self): - project = Project.objects.get(slug="pip") - version = project.versions.get(slug=LATEST) - version.type = EXTERNAL - version.verbose_name = "4" - version.save() - self.url = ( - reverse("footer_html") - + f"?project={project.slug}&version={version.slug}&page=index&docroot=/" - ) - - git_provider_name = "readthedocs.projects.models.Project.git_provider_name" - with mock.patch(git_provider_name, GITHUB_BRAND): - r = self.render() - self.assertIn("#4 (PR)", r.data["html"]) - self.assertNotIn("#4 (MR)", r.data["html"]) - self.assertNotIn("#4 (EV)", r.data["html"]) - with mock.patch(git_provider_name, GITLAB_BRAND): - r = self.render() - self.assertIn("#4 (MR)", r.data["html"]) - self.assertNotIn("#4 (PR)", r.data["html"]) - self.assertNotIn("#4 (EV)", r.data["html"]) - - def test_footer_dont_show_version_warning_for_external_versions(self): - self.latest.type = EXTERNAL - self.latest.save() - - r = self.render() - self.assertEqual(r.status_code, 200) - self.assertFalse(r.data["version_compare"]["is_highest"]) - self.assertFalse(r.data["show_version_warning"]) - self.assertEqual(r.context["main_project"], self.pip) - - def test_footer_uses_version_compare(self): - version_compare = ( - "readthedocs.api.v2.views.footer_views.get_version_compare_data" # noqa - ) - with mock.patch(version_compare) as get_version_compare_data: - get_version_compare_data.return_value = { - "MOCKED": True, - } - r = self.render() - self.assertEqual(r.status_code, 200) - self.assertEqual(r.data["version_compare"], {"MOCKED": True}) - - def test_pdf_build_mentioned_in_footer(self): - self.latest.has_pdf = True - self.latest.save() - - response = self.render() - self.assertIn("pdf", response.data["html"]) - - def test_pdf_not_mentioned_in_footer_when_doesnt_exists(self): - response = self.render() - self.assertNotIn("pdf", response.data["html"]) - - def test_epub_build_mentioned_in_footer(self): - self.latest.has_epub = True - self.latest.save() - - response = self.render() - self.assertIn("epub", response.data["html"]) - - def test_epub_not_mentioned_in_footer_when_doesnt_exists(self): - response = self.render() - self.assertNotIn("epub", response.data["html"]) - - def test_show_version_warning(self): - self.pip.show_version_warning = True - self.pip.save() - response = self.render() - self.assertTrue(response.data["show_version_warning"]) - - def test_show_edit_on_github(self): - version = self.pip.versions.get(slug=LATEST) - version.type = BRANCH - version.save() - response = self.render() - self.assertIn("On GitHub", response.data["html"]) - self.assertIn("View", response.data["html"]) - self.assertIn("Edit", response.data["html"]) - - def test_not_show_edit_on_github(self): - version = self.pip.versions.get(slug=LATEST) - version.type = TAG - version.save() - response = self.render() - self.assertIn("On GitHub", response.data["html"]) - self.assertIn("View", response.data["html"]) - self.assertNotIn("Edit", response.data["html"]) - - def test_index_pages_sphinx_htmldir(self): - version = self.pip.versions.get(slug=LATEST) - version.documentation_type = "sphinx_htmldir" - version.save() - - # A page with slug 'index' should render like /en/latest/ - self.url = ( - reverse("footer_html") - + f"?project={self.pip.slug}&version={self.latest.slug}&page=index&docroot=/" - ) - response = self.render() - self.assertIn("/en/latest/", response.data["html"]) - self.assertNotIn("/en/latest/index.html", response.data["html"]) - - # A page with slug 'foo/index' should render like /en/latest/foo/ - self.url = ( - reverse("footer_html") - + f"?project={self.pip.slug}&version={self.latest.slug}&page=foo/index&docroot=/" - ) - response = self.render() - self.assertIn("/en/latest/foo/", response.data["html"]) - self.assertNotIn("/en/latest/foo.html", response.data["html"]) - self.assertNotIn("/en/latest/foo/index.html", response.data["html"]) - - # A page with slug 'foo/bar' should render like /en/latest/foo/bar/ - self.url = ( - reverse("footer_html") - + f"?project={self.pip.slug}&version={self.latest.slug}&page=foo/bar&docroot=/" - ) - response = self.render() - self.assertIn("/en/latest/foo/bar/", response.data["html"]) - self.assertNotIn("/en/latest/foo/bar.html", response.data["html"]) - self.assertNotIn("/en/latest/foo/bar/index.html", response.data["html"]) - - # A page with slug 'foo/bar/index' should render like /en/latest/foo/bar/ - self.url = ( - reverse("footer_html") - + f"?project={self.pip.slug}&version={self.latest.slug}&page=foo/bar/index&docroot=/" - ) - response = self.render() - self.assertIn("/en/latest/foo/bar/", response.data["html"]) - self.assertNotIn("/en/latest/foo/bar.html", response.data["html"]) - self.assertNotIn("/en/latest/foo/bar/index.html", response.data["html"]) - - # A page with slug 'foo/index/bar' should render like /en/latest/foo/index/bar/ - self.url = ( - reverse("footer_html") - + f"?project={self.pip.slug}&version={self.latest.slug}&page=foo/index/bar&docroot=/" - ) - response = self.render() - self.assertIn("/en/latest/foo/index/bar/", response.data["html"]) - self.assertNotIn("/en/latest/foo/index/bar.html", response.data["html"]) - self.assertNotIn("/en/latest/foo/index/bar/index.html", response.data["html"]) - - def test_hidden_versions(self): - hidden_version = get( - Version, - slug="2.0", - hidden=True, - privacy_level=PUBLIC, - project=self.pip, - ) - - # The hidden version doesn't appear on the footer - self.url = ( - reverse("footer_html") - + f"?project={self.pip.slug}&version={self.latest.slug}&page=index&docroot=/" - ) - response = self.render() - self.assertIn("/en/latest/", response.data["html"]) - self.assertNotIn("/en/2.0/", response.data["html"]) - - # We can access the hidden version, but it doesn't appear on the footer - self.url = ( - reverse("footer_html") - + f"?project={self.pip.slug}&version={hidden_version.slug}&page=index&docroot=/" - ) - response = self.render() - self.assertIn("/en/latest/", response.data["html"]) - self.assertNotIn("/en/2.0/", response.data["html"]) - - def test_built_versions(self): - built_version = get( - Version, - slug="2.0", - active=True, - built=True, - privacy_level=PUBLIC, - project=self.pip, - ) - - # The built versions appears on the footer - self.url = ( - reverse("footer_html") - + f"?project={self.pip.slug}&version={self.latest.slug}&page=index&docroot=/" - ) - response = self.render() - self.assertIn("/en/latest/", response.data["html"]) - self.assertIn("/en/2.0/", response.data["html"]) - - # We can access the built version, and it appears on the footer - self.url = ( - reverse("footer_html") - + f"?project={self.pip.slug}&version={built_version.slug}&page=index&docroot=/" - ) - response = self.render() - self.assertIn("/en/latest/", response.data["html"]) - self.assertIn("/en/2.0/", response.data["html"]) - - def test_not_built_versions(self): - not_built_version = get( - Version, - slug="2.0", - active=True, - built=False, - privacy_level=PUBLIC, - project=self.pip, - ) - - # The un-built version doesn't appear on the footer - self.url = ( - reverse("footer_html") - + f"?project={self.pip.slug}&version={self.latest.slug}&page=index&docroot=/" - ) - response = self.render() - self.assertIn("/en/latest/", response.data["html"]) - self.assertNotIn("/en/2.0/", response.data["html"]) - - # We can access the unbuilt version, but it doesn't appear on the footer - self.url = ( - reverse("footer_html") - + f"?project={self.pip.slug}&version={not_built_version.slug}&page=index&docroot=/" - ) - response = self.render() - self.assertIn("/en/latest/", response.data["html"]) - self.assertNotIn("/en/2.0/", response.data["html"]) - - def test_invalid_footer_url(self): - # The built versions appears on the footer - self.url = reverse("footer_html") + "/invalid/url" - response = self.render() - self.assertEqual(response.status_code, 404) - - -class TestFooterHTML(BaseTestFooterHTML, TestCase): - pass - - -@override_settings( - PUBLIC_DOMAIN="readthedocs.io", - PUBLIC_DOMAIN_USES_HTTPS=True, -) -class TestVersionCompareFooter(TestCase): - fixtures = ["test_data", "eric"] - - def setUp(self): - self.pip = Project.objects.get(slug="pip") - self.pip.versions.update(built=True) - self.pip.show_version_warning = True - self.pip.privacy_level = PUBLIC - self.pip.save() - self.pip.versions.update(privacy_level=PUBLIC) - - self.user = User.objects.get(username="eric") - - def test_highest_version_from_stable(self): - base_version = self.pip.get_stable_version() - valid_data = { - "project": "0.8.1", - "url": "https://pip.readthedocs.io/en/0.8.1/", - "slug": "0.8.1", - "version": "0.8.1", - "is_highest": True, - } - returned_data = get_version_compare_data(self.pip, base_version) - self.assertDictEqual(valid_data, returned_data) - - def test_highest_version_from_lower(self): - base_version = self.pip.versions.get(slug="0.8") - valid_data = { - "project": "0.8.1", - "url": "https://pip.readthedocs.io/en/0.8.1/", - "slug": "0.8.1", - "version": "0.8.1", - "is_highest": False, - } - returned_data = get_version_compare_data(self.pip, base_version) - self.assertDictEqual(valid_data, returned_data) - - def test_highest_version_from_latest(self): - self.pip.versions.filter(slug=LATEST).update(built=True) - base_version = self.pip.versions.get(slug=LATEST) - valid_data = { - "project": "0.8.1", - "url": "https://pip.readthedocs.io/en/0.8.1/", - "slug": "0.8.1", - "version": "0.8.1", - "is_highest": True, - } - returned_data = get_version_compare_data(self.pip, base_version) - self.assertDictEqual(valid_data, returned_data) - - def test_highest_version_over_branches(self): - Version.objects.create( - project=self.pip, - verbose_name="2.0.0", - identifier="2.0.0", - type=BRANCH, - active=True, - ) - - version = Version.objects.create( - project=self.pip, - verbose_name="1.0.0", - identifier="1.0.0", - type=TAG, - active=True, - built=True, - ) - - base_version = self.pip.versions.get(slug="0.8.1") - valid_data = { - "project": "1.0.0", - "url": "https://pip.readthedocs.io/en/1.0.0/", - "slug": "1.0.0", - "version": "1.0.0", - "is_highest": False, - } - returned_data = get_version_compare_data(self.pip, base_version) - self.assertDictEqual(valid_data, returned_data) - - def test_highest_version_without_tags(self): - self.pip.versions.filter(type=TAG).update(type=BRANCH) - - base_version = self.pip.versions.get(slug="0.8.1") - valid_data = { - "project": "0.8.1", - "url": "https://pip.readthedocs.io/en/0.8.1/", - "slug": "0.8.1", - "version": "0.8.1", - "is_highest": True, - } - returned_data = get_version_compare_data(self.pip, base_version) - self.assertDictEqual(valid_data, returned_data) - - base_version = self.pip.versions.get(slug="0.8") - valid_data = { - "project": "0.8.1", - "url": "https://pip.readthedocs.io/en/0.8.1/", - "slug": "0.8.1", - "version": "0.8.1", - "is_highest": False, - } - returned_data = get_version_compare_data(self.pip, base_version) - self.assertDictEqual(valid_data, returned_data) - - version = Version.objects.create( - project=self.pip, - verbose_name="2.0.0", - identifier="2.0.0", - type=BRANCH, - active=True, - built=True, - ) - valid_data = { - "project": "2.0.0", - "url": "https://pip.readthedocs.io/en/2.0.0/", - "slug": "2.0.0", - "version": "2.0.0", - "is_highest": False, - } - returned_data = get_version_compare_data(self.pip, base_version) - self.assertDictEqual(valid_data, returned_data) - - @override_settings( - RTD_ALLOW_ORGANIZATIONS=True, - ) - def test_private_highest_version(self): - get(Organization, projects=[self.pip], owners=[self.user]) - self.pip.versions.update(privacy_level=PRIVATE) - base_version = self.pip.versions.get(slug="0.8") - returned_data = get_version_compare_data(self.pip, base_version) - self.assertTrue(returned_data["is_highest"]) - - returned_data = get_version_compare_data(self.pip, base_version, user=self.user) - valid_data = { - "project": "0.8.1", - "url": "https://pip.readthedocs.io/en/0.8.1/", - "slug": "0.8.1", - "version": "0.8.1", - "is_highest": False, - } - self.assertDictEqual(valid_data, returned_data) - - -@pytest.mark.proxito -@override_settings( - PUBLIC_DOMAIN="readthedocs.io", - RTD_DEFAULT_FEATURES=dict([RTDProductFeature(type=TYPE_CNAME, value=2).to_item()]), -) -class TestFooterPerformance(TestCase): - # The expected number of queries for generating the footer - # This shouldn't increase unless we modify the footer API - EXPECTED_QUERIES = 11 - - def setUp(self): - self.pip = get( - Project, - slug="pip", - repo="https://github.com/rtfd/readthedocs.org", - privacy_level=PUBLIC, - show_version_warning=True, - main_language_project=None, - ) - self.pip.versions.create( - verbose_name="0.8.1", - identifier="0.8.1", - type=TAG, - ) - self.pip.versions.update(privacy_level=PUBLIC, built=True, active=True) - self.latest = self.pip.versions.get(slug=LATEST) - - self.url = ( - reverse("footer_html") - + f"?project={self.pip.slug}&version={self.latest.slug}&page=index&docroot=/docs/" - ) - self.host = "pip.readthedocs.io" - - def test_version_queries(self): - with self.assertNumQueries(self.EXPECTED_QUERIES): - response = self.client.get(self.url, headers={"host": self.host}) - self.assertContains(response, "0.8.1") - - # Second time we don't create a new page view, - # this shouldn't impact the number of queries. - with self.assertNumQueries(self.EXPECTED_QUERIES): - response = self.client.get(self.url, headers={"host": self.host}) - self.assertContains(response, "0.8.1") - - # The number of Versions shouldn't impact the number of queries - for patch in range(3): - identifier = "0.99.{}".format(patch) - self.pip.versions.create( - verbose_name=identifier, - identifier=identifier, - type=TAG, - active=True, - built=True, - ) - - with self.assertNumQueries(self.EXPECTED_QUERIES): - response = self.client.get(self.url, headers={"host": self.host}) - self.assertContains(response, "0.99.0") - - def test_domain_queries(self): - domain = "docs.foobar.com" - self.pip.domains.create( - domain=f"http://{domain}", - canonical=True, - ) - - with self.assertNumQueries(self.EXPECTED_QUERIES): - response = self.client.get(self.url, headers={"host": domain}) - self.assertContains(response, domain) diff --git a/readthedocs/rtd_tests/tests/test_privacy_urls.py b/readthedocs/rtd_tests/tests/test_privacy_urls.py index ace93325952..54319deb9c8 100644 --- a/readthedocs/rtd_tests/tests/test_privacy_urls.py +++ b/readthedocs/rtd_tests/tests/test_privacy_urls.py @@ -439,9 +439,6 @@ def setUp(self): "buildcommandresult-detail": {"pk": self.build_command_result.pk}, "version-detail": {"pk": self.pip.versions.all()[0].pk}, "domain-detail": {"pk": self.domain.pk}, - "footer_html": { - "data": {"project": "pip", "version": "latest", "page": "index"} - }, "remoteorganization-detail": {"pk": self.remote_org.pk}, "remoterepository-detail": {"pk": self.remote_repo.pk}, "remoteaccount-detail": {"pk": self.social_account.pk}, diff --git a/readthedocs/rtd_tests/tests/test_resolver.py b/readthedocs/rtd_tests/tests/test_resolver.py index 87e9cb97cce..fa4d78de13a 100644 --- a/readthedocs/rtd_tests/tests/test_resolver.py +++ b/readthedocs/rtd_tests/tests/test_resolver.py @@ -606,9 +606,6 @@ def test_resolve_project_object(self): url = resolver.resolve_project(self.pip, filename="index.html") self.assertEqual(url, "http://pip.readthedocs.org/index.html") - url = resolver.resolve_project(self.pip, filename="/_/api/v2/footer_html") - self.assertEqual(url, "http://pip.readthedocs.org/_/api/v2/footer_html") - def test_resolve_subproject_object(self): url = resolver.resolve_project(self.subproject) self.assertEqual(url, "http://pip.readthedocs.org/") @@ -616,11 +613,6 @@ def test_resolve_subproject_object(self): url = resolver.resolve_project(self.subproject, filename="index.html") self.assertEqual(url, "http://pip.readthedocs.org/index.html") - url = resolver.resolve_project( - self.subproject, filename="/_/api/v2/footer_html" - ) - self.assertEqual(url, "http://pip.readthedocs.org/_/api/v2/footer_html") - def test_resolve_translation_object(self): url = resolver.resolve_project(self.translation) self.assertEqual(url, "http://pip.readthedocs.org/") @@ -628,11 +620,6 @@ def test_resolve_translation_object(self): url = resolver.resolve_project(self.translation, filename="index.html") self.assertEqual(url, "http://pip.readthedocs.org/index.html") - url = resolver.resolve_project( - self.translation, filename="/_/api/v2/footer_html" - ) - self.assertEqual(url, "http://pip.readthedocs.org/_/api/v2/footer_html") - def test_resolve_version_object(self): url = resolver.resolve_version(self.pip) self.assertEqual(url, "http://pip.readthedocs.org/en/latest/") diff --git a/readthedocs/settings/base.py b/readthedocs/settings/base.py index 58626c017dd..cc119dc6644 100644 --- a/readthedocs/settings/base.py +++ b/readthedocs/settings/base.py @@ -756,8 +756,7 @@ def SOCIALACCOUNT_PROVIDERS(self): CORS_URLS_REGEX = re.compile( r""" ^( - /api/v2/footer_html - |/api/v2/search + /api/v2/search |/api/v2/docsearch |/api/v2/embed |/api/v3/embed