From 0ef140a691bb34cd659f9f9880aad68ce55c0e66 Mon Sep 17 00:00:00 2001 From: Santos Gallegos Date: Wed, 9 Dec 2020 13:22:24 -0500 Subject: [PATCH 1/2] Analytics: move page views to its own endpoint This allow us to cache the footer response and keep receiving page views. --- readthedocs/analytics/apps.py | 5 - readthedocs/analytics/proxied_api.py | 98 +++++++++++++++++++ readthedocs/analytics/signals.py | 44 --------- readthedocs/analytics/tests.py | 4 +- readthedocs/api/v2/proxied_urls.py | 5 +- readthedocs/api/v2/signals.py | 8 -- readthedocs/api/v2/views/footer_views.py | 13 +-- .../static-src/core/js/doc-embed/footer.js | 13 +++ .../static/core/js/readthedocs-doc-embed.js | 2 +- readthedocs/rtd_tests/tests/test_footer.py | 2 +- 10 files changed, 120 insertions(+), 74 deletions(-) create mode 100644 readthedocs/analytics/proxied_api.py delete mode 100644 readthedocs/analytics/signals.py delete mode 100644 readthedocs/api/v2/signals.py diff --git a/readthedocs/analytics/apps.py b/readthedocs/analytics/apps.py index 7b11e7709de..cef2211aa3b 100644 --- a/readthedocs/analytics/apps.py +++ b/readthedocs/analytics/apps.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - """Django app config for the analytics app.""" from django.apps import AppConfig @@ -11,6 +9,3 @@ class AnalyticsAppConfig(AppConfig): name = 'readthedocs.analytics' verbose_name = 'Analytics' - - def ready(self): - from . import signals # noqa diff --git a/readthedocs/analytics/proxied_api.py b/readthedocs/analytics/proxied_api.py new file mode 100644 index 00000000000..80cb629c59b --- /dev/null +++ b/readthedocs/analytics/proxied_api.py @@ -0,0 +1,98 @@ +"""Analytics views that are severd from the same domain as the docs.""" + +from django.db.models import F +from django.shortcuts import get_object_or_404 +from django.utils import timezone +from rest_framework.response import Response +from rest_framework.views import APIView + +from readthedocs.analytics.models import PageView +from readthedocs.api.v2.permissions import IsAuthorizedToViewVersion +from readthedocs.core.unresolver import unresolve_from_request +from readthedocs.core.utils.extend import SettingsOverrideObject +from readthedocs.projects.models import Feature, Project + + +class BaseAnalyticsView(APIView): + + """ + Track page views. + + Query parameters: + + - project + - version + - absolute_uri: Full path with domain. + """ + + http_method_names = ['get'] + permission_classes = [IsAuthorizedToViewVersion] + + def _get_project(self): + cache_key = '__cached_project' + project = getattr(self, cache_key, None) + + if not project: + project_slug = self.request.GET.get('project') + project = get_object_or_404(Project, slug=project_slug) + setattr(self, cache_key, project) + + return project + + def _get_version(self): + cache_key = '__cached_version' + version = getattr(self, cache_key, None) + + if not version: + version_slug = self.request.GET.get('version') + project = self._get_project() + version = get_object_or_404( + project.versions.all(), + slug=version_slug, + ) + setattr(self, cache_key, version) + + return version + + # pylint: disable=unused-argument + def get(self, request, *args, **kwargs): + project = self._get_project() + version = self._get_version() + absolute_uri = self.request.GET.get('absolute_uri') + self.increase_page_view_count( + request=request, + project=project, + version=version, + absolute_uri=absolute_uri, + ) + return Response(status=200) + + # pylint: disable=no-self-use + def increase_page_view_count(self, request, project, version, absolute_uri): + """Increase the page view count for the given project.""" + if not absolute_uri or not project.has_feature(Feature.STORE_PAGEVIEWS): + return + + unresolved = unresolve_from_request(request, absolute_uri) + if not unresolved: + return + + path = unresolved.filename + + fields = dict( + project=project, + version=version, + path=path, + date=timezone.now().date(), + ) + page_view = PageView.objects.filter(**fields).first() + if page_view: + page_view.view_count = F('view_count') + 1 + page_view.save(update_fields=['view_count']) + else: + PageView.objects.create(**fields, view_count=1) + + +class AnalyticsView(SettingsOverrideObject): + + _default_class = BaseAnalyticsView diff --git a/readthedocs/analytics/signals.py b/readthedocs/analytics/signals.py deleted file mode 100644 index fb95616c609..00000000000 --- a/readthedocs/analytics/signals.py +++ /dev/null @@ -1,44 +0,0 @@ -"""Django signals for the analytics app.""" -from django.db.models import F -from django.dispatch import receiver -from django.utils import timezone - -from readthedocs.api.v2.signals import footer_response -from readthedocs.core.unresolver import unresolve_from_request -from readthedocs.projects.models import Feature - -from .models import PageView - - -@receiver(footer_response) -def increase_page_view_count(sender, *, request, context, absolute_uri, **kwargs): - """Increase the page view count for the given project.""" - # unused - del sender - del kwargs - - project = context['project'] - version = context['version'] - - if not absolute_uri or not project.has_feature(Feature.STORE_PAGEVIEWS): - return - - unresolved = unresolve_from_request(request, absolute_uri) - if not unresolved: - return - - path = unresolved.filename - - fields = dict( - project=project, - version=version, - path=path, - date=timezone.now().date(), - ) - - page_view = PageView.objects.filter(**fields).first() - if page_view: - page_view.view_count = F('view_count') + 1 - page_view.save(update_fields=['view_count']) - else: - PageView.objects.create(**fields, view_count=1) diff --git a/readthedocs/analytics/tests.py b/readthedocs/analytics/tests.py index 409952bb419..8360638ad33 100644 --- a/readthedocs/analytics/tests.py +++ b/readthedocs/analytics/tests.py @@ -109,8 +109,8 @@ def setUp(self): self.absolute_uri = f'https://{self.project.slug}.readthedocs.io/en/latest/index.html' self.host = f'{self.project.slug}.readthedocs.io' self.url = ( - reverse('footer_html') + - f'?project={self.project.slug}&version={self.version.slug}&page=index&docroot=/docs/' + + reverse('analytics_api') + + f'?project={self.project.slug}&version={self.version.slug}' f'&absolute_uri={self.absolute_uri}' ) diff --git a/readthedocs/api/v2/proxied_urls.py b/readthedocs/api/v2/proxied_urls.py index 99ed44165f3..c9d42e67195 100644 --- a/readthedocs/api/v2/proxied_urls.py +++ b/readthedocs/api/v2/proxied_urls.py @@ -8,12 +8,15 @@ from django.conf import settings from django.conf.urls import include, url -from .views.proxied import ProxiedFooterHTML +from readthedocs.analytics.proxied_api import AnalyticsView from readthedocs.search.proxied_api import ProxiedPageSearchAPIView +from .views.proxied import ProxiedFooterHTML + api_footer_urls = [ url(r'footer_html/', ProxiedFooterHTML.as_view(), name='footer_html'), url(r'search/$', ProxiedPageSearchAPIView.as_view(), name='search_api'), + url(r'analytics/$', AnalyticsView.as_view(), name='analytics_api'), ] urlpatterns = api_footer_urls diff --git a/readthedocs/api/v2/signals.py b/readthedocs/api/v2/signals.py deleted file mode 100644 index dfb06d77540..00000000000 --- a/readthedocs/api/v2/signals.py +++ /dev/null @@ -1,8 +0,0 @@ -"""We define custom Django signals to trigger when a footer is rendered.""" - -import django.dispatch - - -footer_response = django.dispatch.Signal( - providing_args=['request', 'context', 'response_data', 'absolute_uri'], -) diff --git a/readthedocs/api/v2/views/footer_views.py b/readthedocs/api/v2/views/footer_views.py index 759244a4ee1..0e310b55820 100644 --- a/readthedocs/api/v2/views/footer_views.py +++ b/readthedocs/api/v2/views/footer_views.py @@ -11,12 +11,11 @@ from rest_framework_jsonp.renderers import JSONPRenderer from readthedocs.api.v2.permissions import IsAuthorizedToViewVersion -from readthedocs.api.v2.signals import footer_response 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 Feature, Project +from readthedocs.projects.models import Project from readthedocs.projects.version_handling import ( highest_version, parse_version_failsafe, @@ -243,16 +242,6 @@ def get(self, request, format=None): 'version_supported': version.supported, } - # Allow folks to hook onto the footer response for various information - # collection, or to modify the resp_data. - footer_response.send( - sender=None, - request=request, - context=context, - response_data=resp_data, - absolute_uri=self.request.GET.get('absolute_uri'), - ) - return Response(resp_data) diff --git a/readthedocs/core/static-src/core/js/doc-embed/footer.js b/readthedocs/core/static-src/core/js/doc-embed/footer.js index b7fc76f3ab0..f8672ed5438 100644 --- a/readthedocs/core/static-src/core/js/doc-embed/footer.js +++ b/readthedocs/core/static-src/core/js/doc-embed/footer.js @@ -83,6 +83,19 @@ function init() { console.error('Error loading Read the Docs footer'); } }); + + // Register page view. + $.ajax({ + url: rtd.proxied_api_host + "/api/v2/analytics/", + data: { + project: rtd['project'], + version: rtd['version'], + absolute_uri: window.location.href, + }, + error: function () { + console.error('Error registering page view'); + } + }); } module.exports = { diff --git a/readthedocs/core/static/core/js/readthedocs-doc-embed.js b/readthedocs/core/static/core/js/readthedocs-doc-embed.js index 602884485fc..b3d2b566408 100644 --- a/readthedocs/core/static/core/js/readthedocs-doc-embed.js +++ b/readthedocs/core/static/core/js/readthedocs-doc-embed.js @@ -1 +1 @@ -!function o(a,s,l){function c(t,e){if(!s[t]){if(!a[t]){var i="function"==typeof require&&require;if(!e&&i)return i(t,!0);if(d)return d(t,!0);var n=new Error("Cannot find module '"+t+"'");throw n.code="MODULE_NOT_FOUND",n}var r=s[t]={exports:{}};a[t][0].call(r.exports,function(e){return c(a[t][1][e]||e)},r,r.exports,o,a,s,l)}return s[t].exports}for(var d="function"==typeof require&&require,e=0;e"),i("table.docutils.footnote").wrap("
"),i("table.docutils.citation").wrap("
"),i(".wy-menu-vertical ul").not(".simple").siblings("a").each(function(){var t=i(this);expand=i(''),expand.on("click",function(e){return n.toggleCurrent(t),e.stopPropagation(),!1}),t.prepend(expand)})},reset:function(){var e=encodeURI(window.location.hash)||"#";try{var t=$(".wy-menu-vertical"),i=t.find('[href="'+e+'"]');if(0===i.length){var n=$('.document [id="'+e.substring(1)+'"]').closest("div.section");0===(i=t.find('[href="#'+n.attr("id")+'"]')).length&&(i=t.find('[href="#"]'))}0this.docHeight||(this.navBar.scrollTop(i),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){var t=e.closest("li");t.siblings("li.current").removeClass("current"),t.siblings().find("li.current").removeClass("current"),t.find("> ul li.current").removeClass("current"),t.toggleClass("current")}},"undefined"!=typeof window&&(window.SphinxRtdTheme={Navigation:t.exports.ThemeNav,StickyNav:t.exports.ThemeNav}),function(){for(var o=0,e=["ms","moz","webkit","o"],t=0;t/g,u=/"/g,h=/"/g,p=/&#([a-zA-Z0-9]*);?/gim,f=/:?/gim,g=/&newline;?/gim,m=/((j\s*a\s*v\s*a|v\s*b|l\s*i\s*v\s*e)\s*s\s*c\s*r\s*i\s*p\s*t\s*|m\s*o\s*c\s*h\s*a)\:/gi,v=/e\s*x\s*p\s*r\s*e\s*s\s*s\s*i\s*o\s*n\s*\(.*/gi,b=/u\s*r\s*l\s*\(.*/gi;function w(e){return e.replace(u,""")}function y(e){return e.replace(h,'"')}function _(e){return e.replace(p,function(e,t){return"x"===t[0]||"X"===t[0]?String.fromCharCode(parseInt(t.substr(1),16)):String.fromCharCode(parseInt(t,10))})}function x(e){return e.replace(f,":").replace(g," ")}function k(e){for(var t="",i=0,n=e.length;i/g;i.whiteList={a:["target","href","title"],abbr:["title"],address:[],area:["shape","coords","href","alt"],article:[],aside:[],audio:["autoplay","controls","loop","preload","src"],b:[],bdi:["dir"],bdo:["dir"],big:[],blockquote:["cite"],br:[],caption:[],center:[],cite:[],code:[],col:["align","valign","span","width"],colgroup:["align","valign","span","width"],dd:[],del:["datetime"],details:["open"],div:[],dl:[],dt:[],em:[],font:["color","size","face"],footer:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],header:[],hr:[],i:[],img:["src","alt","title","width","height"],ins:["datetime"],li:[],mark:[],nav:[],ol:[],p:[],pre:[],s:[],section:[],small:[],span:[],sub:[],sup:[],strong:[],table:["width","border","align","valign"],tbody:["align","valign"],td:["width","rowspan","colspan","align","valign"],tfoot:["align","valign"],th:["width","rowspan","colspan","align","valign"],thead:["align","valign"],tr:["rowspan","align","valign"],tt:[],u:[],ul:[],video:["autoplay","controls","loop","preload","src","height","width"]},i.getDefaultWhiteList=o,i.onTag=function(e,t,i){},i.onIgnoreTag=function(e,t,i){},i.onTagAttr=function(e,t,i){},i.onIgnoreTagAttr=function(e,t,i){},i.safeAttrValue=function(e,t,i,n){if(i=T(i),"href"===t||"src"===t){if("#"===(i=d.trim(i)))return"#";if("http://"!==i.substr(0,7)&&"https://"!==i.substr(0,8)&&"mailto:"!==i.substr(0,7)&&"tel:"!==i.substr(0,4)&&"#"!==i[0]&&"/"!==i[0])return""}else if("background"===t){if(m.lastIndex=0,m.test(i))return""}else if("style"===t){if(v.lastIndex=0,v.test(i))return"";if(b.lastIndex=0,b.test(i)&&(m.lastIndex=0,m.test(i)))return"";!1!==n&&(i=(n=n||a).process(i))}return i=E(i)},i.escapeHtml=s,i.escapeQuote=w,i.unescapeQuote=y,i.escapeHtmlEntities=_,i.escapeDangerHtml5Entities=x,i.clearNonPrintableCharacter=k,i.friendlyAttrValue=T,i.escapeAttrValue=E,i.onIgnoreTagStripAll=function(){return""},i.StripTagBody=function(o,a){"function"!=typeof a&&(a=function(){});var s=!Array.isArray(o),l=[],c=!1;return{onIgnoreTag:function(e,t,i){if(function(e){return s||-1!==d.indexOf(o,e)}(e)){if(i.isClosing){var n="[/removed]",r=i.position+n.length;return l.push([!1!==c?c:i.position,r]),c=!1,n}return c=c||i.position,"[removed]"}return a(e,t,i)},remove:function(t){var i="",n=0;return d.forEach(l,function(e){i+=t.slice(n,e[0]),n=e[1]}),i+=t.slice(n)}}},i.stripCommentTag=function(e){return e.replace(S,"")},i.stripBlankChar=function(e){var t=e.split("");return(t=t.filter(function(e){var t=e.charCodeAt(0);return 127!==t&&(!(t<=31)||(10===t||13===t))})).join("")},i.cssFilter=a,i.getDefaultCSSWhiteList=r},{"./util":5,cssfilter:9}],3:[function(e,t,i){var n=e("./default"),r=e("./parser"),o=e("./xss");for(var a in(i=t.exports=function(e,t){return new o(t).process(e)}).FilterXSS=o,n)i[a]=n[a];for(var a in r)i[a]=r[a];"undefined"!=typeof window&&(window.filterXSS=t.exports)},{"./default":2,"./parser":4,"./xss":6}],4:[function(e,t,i){var d=e("./util");function h(e){var t=d.spaceIndex(e);if(-1===t)var i=e.slice(1,-1);else i=e.slice(1,t+1);return"/"===(i=d.trim(i).toLowerCase()).slice(0,1)&&(i=i.slice(1)),"/"===i.slice(-1)&&(i=i.slice(0,-1)),i}var u=/[^a-zA-Z0-9_:\.\-]/gim;function p(e,t){for(;t"===u){n+=i(e.slice(r,o)),d=h(c=e.slice(o,s+1)),n+=t(o,n.length,d,c,"";var s=function(e){var t=w.spaceIndex(e);if(-1===t)return{html:"",closing:"/"===e[e.length-2]};var i="/"===(e=w.trim(e.slice(t+1,-1)))[e.length-1];return i&&(e=w.trim(e.slice(0,-1))),{html:e,closing:i}}(i),l=d[r],c=b(s.html,function(e,t){var i,n=-1!==w.indexOf(l,e);return y(i=p(r,e,t,n))?n?(t=g(r,e,t,v))?e+'="'+t+'"':e:y(i=f(r,e,t,n))?void 0:i:i});i="<"+r;return c&&(i+=" "+c),s.closing&&(i+=" /"),i+=">"}return y(o=h(r,i,a))?m(i):o},m);return i&&(n=i.remove(n)),n},t.exports=s},{"./default":2,"./parser":4,"./util":5,cssfilter:9}],7:[function(e,t,i){var n=e("./default"),r=e("./parser");e("./util");function h(e){return null==e}function o(e){(e=e||{}).whiteList=e.whiteList||n.whiteList,e.onAttr=e.onAttr||n.onAttr,e.onIgnoreAttr=e.onIgnoreAttr||n.onIgnoreAttr,this.options=e}o.prototype.process=function(e){if(!(e=(e=e||"").toString()))return"";var t=this.options,c=t.whiteList,d=t.onAttr,u=t.onIgnoreAttr;return r(e,function(e,t,i,n,r){var o=c[i],a=!1;!0===o?a=o:"function"==typeof o?a=o(n):o instanceof RegExp&&(a=o.test(n)),!0!==a&&(a=!1);var s,l={position:t,sourcePosition:e,source:r,isWhite:a};return a?h(s=d(i,n,l))?i+":"+n:s:h(s=u(i,n,l))?void 0:s})},t.exports=o},{"./default":8,"./parser":10,"./util":11}],8:[function(e,t,i){function n(){var e={"align-content":!1,"align-items":!1,"align-self":!1,"alignment-adjust":!1,"alignment-baseline":!1,all:!1,"anchor-point":!1,animation:!1,"animation-delay":!1,"animation-direction":!1,"animation-duration":!1,"animation-fill-mode":!1,"animation-iteration-count":!1,"animation-name":!1,"animation-play-state":!1,"animation-timing-function":!1,azimuth:!1,"backface-visibility":!1,background:!0,"background-attachment":!0,"background-clip":!0,"background-color":!0,"background-image":!0,"background-origin":!0,"background-position":!0,"background-repeat":!0,"background-size":!0,"baseline-shift":!1,binding:!1,bleed:!1,"bookmark-label":!1,"bookmark-level":!1,"bookmark-state":!1,border:!0,"border-bottom":!0,"border-bottom-color":!0,"border-bottom-left-radius":!0,"border-bottom-right-radius":!0,"border-bottom-style":!0,"border-bottom-width":!0,"border-collapse":!0,"border-color":!0,"border-image":!0,"border-image-outset":!0,"border-image-repeat":!0,"border-image-slice":!0,"border-image-source":!0,"border-image-width":!0,"border-left":!0,"border-left-color":!0,"border-left-style":!0,"border-left-width":!0,"border-radius":!0,"border-right":!0,"border-right-color":!0,"border-right-style":!0,"border-right-width":!0,"border-spacing":!0,"border-style":!0,"border-top":!0,"border-top-color":!0,"border-top-left-radius":!0,"border-top-right-radius":!0,"border-top-style":!0,"border-top-width":!0,"border-width":!0,bottom:!1,"box-decoration-break":!0,"box-shadow":!0,"box-sizing":!0,"box-snap":!0,"box-suppress":!0,"break-after":!0,"break-before":!0,"break-inside":!0,"caption-side":!1,chains:!1,clear:!0,clip:!1,"clip-path":!1,"clip-rule":!1,color:!0,"color-interpolation-filters":!0,"column-count":!1,"column-fill":!1,"column-gap":!1,"column-rule":!1,"column-rule-color":!1,"column-rule-style":!1,"column-rule-width":!1,"column-span":!1,"column-width":!1,columns:!1,contain:!1,content:!1,"counter-increment":!1,"counter-reset":!1,"counter-set":!1,crop:!1,cue:!1,"cue-after":!1,"cue-before":!1,cursor:!1,direction:!1,display:!0,"display-inside":!0,"display-list":!0,"display-outside":!0,"dominant-baseline":!1,elevation:!1,"empty-cells":!1,filter:!1,flex:!1,"flex-basis":!1,"flex-direction":!1,"flex-flow":!1,"flex-grow":!1,"flex-shrink":!1,"flex-wrap":!1,float:!1,"float-offset":!1,"flood-color":!1,"flood-opacity":!1,"flow-from":!1,"flow-into":!1,font:!0,"font-family":!0,"font-feature-settings":!0,"font-kerning":!0,"font-language-override":!0,"font-size":!0,"font-size-adjust":!0,"font-stretch":!0,"font-style":!0,"font-synthesis":!0,"font-variant":!0,"font-variant-alternates":!0,"font-variant-caps":!0,"font-variant-east-asian":!0,"font-variant-ligatures":!0,"font-variant-numeric":!0,"font-variant-position":!0,"font-weight":!0,grid:!1,"grid-area":!1,"grid-auto-columns":!1,"grid-auto-flow":!1,"grid-auto-rows":!1,"grid-column":!1,"grid-column-end":!1,"grid-column-start":!1,"grid-row":!1,"grid-row-end":!1,"grid-row-start":!1,"grid-template":!1,"grid-template-areas":!1,"grid-template-columns":!1,"grid-template-rows":!1,"hanging-punctuation":!1,height:!0,hyphens:!1,icon:!1,"image-orientation":!1,"image-resolution":!1,"ime-mode":!1,"initial-letters":!1,"inline-box-align":!1,"justify-content":!1,"justify-items":!1,"justify-self":!1,left:!1,"letter-spacing":!0,"lighting-color":!0,"line-box-contain":!1,"line-break":!1,"line-grid":!1,"line-height":!1,"line-snap":!1,"line-stacking":!1,"line-stacking-ruby":!1,"line-stacking-shift":!1,"line-stacking-strategy":!1,"list-style":!0,"list-style-image":!0,"list-style-position":!0,"list-style-type":!0,margin:!0,"margin-bottom":!0,"margin-left":!0,"margin-right":!0,"margin-top":!0,"marker-offset":!1,"marker-side":!1,marks:!1,mask:!1,"mask-box":!1,"mask-box-outset":!1,"mask-box-repeat":!1,"mask-box-slice":!1,"mask-box-source":!1,"mask-box-width":!1,"mask-clip":!1,"mask-image":!1,"mask-origin":!1,"mask-position":!1,"mask-repeat":!1,"mask-size":!1,"mask-source-type":!1,"mask-type":!1,"max-height":!0,"max-lines":!1,"max-width":!0,"min-height":!0,"min-width":!0,"move-to":!1,"nav-down":!1,"nav-index":!1,"nav-left":!1,"nav-right":!1,"nav-up":!1,"object-fit":!1,"object-position":!1,opacity:!1,order:!1,orphans:!1,outline:!1,"outline-color":!1,"outline-offset":!1,"outline-style":!1,"outline-width":!1,overflow:!1,"overflow-wrap":!1,"overflow-x":!1,"overflow-y":!1,padding:!0,"padding-bottom":!0,"padding-left":!0,"padding-right":!0,"padding-top":!0,page:!1,"page-break-after":!1,"page-break-before":!1,"page-break-inside":!1,"page-policy":!1,pause:!1,"pause-after":!1,"pause-before":!1,perspective:!1,"perspective-origin":!1,pitch:!1,"pitch-range":!1,"play-during":!1,position:!1,"presentation-level":!1,quotes:!1,"region-fragment":!1,resize:!1,rest:!1,"rest-after":!1,"rest-before":!1,richness:!1,right:!1,rotation:!1,"rotation-point":!1,"ruby-align":!1,"ruby-merge":!1,"ruby-position":!1,"shape-image-threshold":!1,"shape-outside":!1,"shape-margin":!1,size:!1,speak:!1,"speak-as":!1,"speak-header":!1,"speak-numeral":!1,"speak-punctuation":!1,"speech-rate":!1,stress:!1,"string-set":!1,"tab-size":!1,"table-layout":!1,"text-align":!0,"text-align-last":!0,"text-combine-upright":!0,"text-decoration":!0,"text-decoration-color":!0,"text-decoration-line":!0,"text-decoration-skip":!0,"text-decoration-style":!0,"text-emphasis":!0,"text-emphasis-color":!0,"text-emphasis-position":!0,"text-emphasis-style":!0,"text-height":!0,"text-indent":!0,"text-justify":!0,"text-orientation":!0,"text-overflow":!0,"text-shadow":!0,"text-space-collapse":!0,"text-transform":!0,"text-underline-position":!0,"text-wrap":!0,top:!1,transform:!1,"transform-origin":!1,"transform-style":!1,transition:!1,"transition-delay":!1,"transition-duration":!1,"transition-property":!1,"transition-timing-function":!1,"unicode-bidi":!1,"vertical-align":!1,visibility:!1,"voice-balance":!1,"voice-duration":!1,"voice-family":!1,"voice-pitch":!1,"voice-range":!1,"voice-rate":!1,"voice-stress":!1,"voice-volume":!1,volume:!1,"white-space":!1,widows:!1,width:!0,"will-change":!1,"word-break":!0,"word-spacing":!0,"word-wrap":!0,"wrap-flow":!1,"wrap-through":!1,"writing-mode":!1,"z-index":!1};return e}i.whiteList=n(),i.getDefaultWhiteList=n,i.onAttr=function(e,t,i){},i.onIgnoreAttr=function(e,t,i){}},{}],9:[function(e,t,i){var n=e("./default"),r=e("./css");for(var o in(i=t.exports=function(e,t){return new r(t).process(e)}).FilterCSS=r,n)i[o]=n[o];"undefined"!=typeof window&&(window.filterCSS=t.exports)},{"./css":7,"./default":8}],10:[function(e,t,i){var u=e("./util");t.exports=function(o,a){";"!==(o=u.trimRight(o))[o.length-1]&&(o+=";");var e=o.length,s=!1,l=0,c=0,d="";function t(){if(!s){var e=u.trim(o.slice(l,c)),t=e.indexOf(":");if(-1!==t){var i=u.trim(e.slice(0,t)),n=u.trim(e.slice(t+1));if(i){var r=a(l,d.length,i,n,e);r&&(d+=r+"; ")}}}l=c+1}for(;c");if(a.append($("

").append($("",{href:r.path,text:r.title}))),r.project!==w){var s="(from project "+r.project+")";a.append($("",{text:s}))}for(var l=0;lj&&(h=h.substr(0,j)+" ...");var p=[h];if(c.highlights.title.length&&(u=c.highlights.title[0]),c.highlights.content.length){var f=c.highlights.content;p=[];for(var g=0;g/g,"").replace(/<\/span>/g,""),a.append($("

").append($("",{href:d}).html(u)));for(var m=0;m/g,"").replace(/<\/span>/g,""),a.append($("

").html(v))}i.append(a)}}}}else console.log("Read the Docs search returned 0 result. Falling back to MkDocs search."),b()}).fail(function(e){console.log("Read the Docs search failed. Falling back to MkDocs search."),b()}),$.ajax({url:t.href,crossDomain:!0,xhrFields:{withCredentials:!0},complete:function(e,t){return"success"!==t||void 0===e.responseJSON||0===e.responseJSON.count?n.reject():n.resolve(e.responseJSON)}}).fail(function(e,t,i){return n.reject()})}function e(){var e=document.getElementById("mkdocs-search-query");e&&e.addEventListener("keyup",n);var t=window.getSearchTermFromLocation();t&&(e.value=t,n())}var w=i.project,r=i.version,o=i.language||"en";$(document).ready(function(){window.doSearchFallback=window.doSearch,window.doSearch=n,(window.initSearch=e)()})}t.exports={init:function(){var e=n.get();e.is_sphinx_builder()?function(t){var A=t.project,i=t.version,r=t.language||"en";if("undefined"!=typeof Search&&A&&i)if(t.features&&t.features.docsearch_disabled)console.log("Server side search is disabled.");else{var e=Search.query;Search.query_fallback=e,Search.query=function(S){var n=$.Deferred(),e=document.createElement("a");e.href=t.proxied_api_host+"/api/v2/search/",e.search="?q="+$.urlencode(S)+"&project="+A+"&version="+i+"&language="+r,n.then(function(e){var t=e.results||[];if(t.length){for(var i=0;i'),a=n.title;n.highlights.title.length&&(a=C(n.highlights.title[0]));var s=n.path+"?highlight="+$.urlencode(S),l=$("",{href:s});if(l.html(a),l.find("span").addClass("highlighted"),o.append(l),n.project!==A){var c=" (from project "+n.project+")",d=$("",{text:c});o.append(d)}for(var u=0;u');if("section"===h.type){var f=h,g=f.title,m=s+"#"+f.id,v=[f.content.substr(0,j)+" ..."];if(f.highlights.title.length&&(g=C(f.highlights.title[0])),f.highlights.content.length){var b=f.highlights.content;v=[];for(var w=0;w<%= section_subtitle %><% for (var i = 0; i < section_content.length; ++i) { %>

<%= section_content[i] %>
<% } %>',{section_subtitle_link:m,section_subtitle:g,section_content:v})}if("domain"===h.type){var y=h,x=y.role,k=s+"#"+y.id,T=y.name,E="";""!==y.content&&(E=y.content.substr(0,j)+" ..."),y.highlights.content.length&&(E="... "+C(y.highlights.content[0])+" ..."),y.highlights.name.length&&(T=C(y.highlights.name[0])),I(p,'
<%= domain_content %>
',{domain_subtitle_link:k,domain_subtitle:"["+x+"]: "+T,domain_content:E})}p.find("span").addClass("highlighted"),o.append(p),u"))}Search.output.append(o),o.slideDown(5)}Search.status.text(_("Search finished, found %s page(s) matching the search query.").replace("%s",t.length))}else console.log("Read the Docs search failed. Falling back to Sphinx search."),Search.query_fallback(S)}).fail(function(e){console.debug("Read the Docs search failed. Falling back to Sphinx search."),Search.query_fallback(S)}).always(function(){$("#search-progress").empty(),Search.stopPulse(),Search.title.text(_("Search Results")),Search.status.fadeIn(500)}),$.ajax({url:e.href,crossDomain:!0,xhrFields:{withCredentials:!0},complete:function(e,t){return"success"!==t||void 0===e.responseJSON||0===e.responseJSON.count?n.reject():n.resolve(e.responseJSON)}}).fail(function(e,t,i){return n.reject()})}}$(document).ready(function(){"undefined"!=typeof Search&&Search.init()})}(e):e.features&&!e.features.docsearch_disabled?r(e):console.log("Server side search is disabled.")}}},{"./../../../../../../bower_components/xss/lib/index":3,"./rtd-data":14}],16:[function(r,e,t){var o=r("./rtd-data");e.exports={init:function(){var e=o.get();if($(document).on("click","[data-toggle='rst-current-version']",function(){var e=$("[data-toggle='rst-versions']").hasClass("shift-up")?"was_open":"was_closed";"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){var t=r("./../../../../../../bower_components/sphinx-rtd-theme/js/theme.js").ThemeNav;if($(document).ready(function(){setTimeout(function(){t.navBar||t.enable()},1e3)}),e.is_rtd_like_theme())if(!$("div.wy-side-scroll:first").length){console.log("Applying theme sidebar fix...");var i=$("nav.wy-nav-side:first"),n=$("
").addClass("wy-side-scroll");i.children().detach().appendTo(n),n.prependTo(i),t.navBar=n}}}}},{"./../../../../../../bower_components/sphinx-rtd-theme/js/theme.js":1,"./rtd-data":14}],17:[function(e,t,i){e("./constants");var r,n=e("./rtd-data"),o="[data-ea-publisher]",a="#ethical-ad-placement";function s(){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 l(){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 c(e){var t=null;e&&(t=e.attr("class","keep-us-sustainable"),$("

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

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

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

").appendTo(n)).offset())||i.top-$(window).scrollTop()+200>$(window).height())&&(r.is_rtd_like_theme()?(n=$("
").insertAfter("footer hr"),e="ethical-rtd"):r.is_alabaster_like_theme()&&(n="div.bodywrapper .body",e="ethical-alabaster")),t.remove(),$("
").attr("id","rtd-sidebar").attr("data-ea-publisher","readthedocs").attr("data-ea-type","readthedocs-sidebar").attr("data-ea-manual","true").addClass(e).appendTo(n)):null)}(),function(){var e=document.createElement("script");e.src="https://media.ethicalads.io/media/client/beta/ethicalads.min.js",e.type="text/javascript",e.async=!0,e.id="ethicaladsjs",document.getElementsByTagName("head")[0].appendChild(e)}(),$.ajax({url:r.api_host+"/api/v2/sustainability/data/",crossDomain:!0,xhrFields:{withCredentials:!0},dataType:"jsonp",data:{format:"jsonp",project:r.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():!r.ad_free&&s()?(l(),c(t)):$("#ethicaladsjs").on("load",function(){"undefined"!=typeof ethicalads&ðicalads.load()}))},error:function(){console.error("Error loading Read the Docs user and project information"),!r.ad_free&&s()&&(l(),c(t))}}))}}},{"./constants":12,"./rtd-data":14}],18:[function(e,t,i){var o=e("./rtd-data");t.exports={init:function(e){var t=o.get();if(!e.is_highest){var i=window.location.pathname.replace(t.version,e.slug),n=$('

Note

You are not reading the most recent version of this documentation. is the latest version available.

');n.find("a").attr("href",i).text(e.slug);var r=$("div.body");r.length||(r=$("div.document")),r.prepend(n)}}}},{"./rtd-data":14}],19:[function(e,t,i){var n,r=e("./doc-embed/sponsorship"),o=e("./doc-embed/footer.js"),a=(e("./doc-embed/rtd-data"),e("./doc-embed/sphinx")),s=e("./doc-embed/search");n=function(){o.init(),a.init(),s.init(),r.init()},"complete"===document.readyState||"interactive"===document.readyState?setTimeout(n,1):document.addEventListener("DOMContentLoaded",n)},{"./doc-embed/footer.js":13,"./doc-embed/rtd-data":14,"./doc-embed/search":15,"./doc-embed/sphinx":16,"./doc-embed/sponsorship":17}]},{},[19]); \ No newline at end of file +!function o(a,s,l){function c(t,e){if(!s[t]){if(!a[t]){var i="function"==typeof require&&require;if(!e&&i)return i(t,!0);if(d)return d(t,!0);var n=new Error("Cannot find module '"+t+"'");throw n.code="MODULE_NOT_FOUND",n}var r=s[t]={exports:{}};a[t][0].call(r.exports,function(e){return c(a[t][1][e]||e)},r,r.exports,o,a,s,l)}return s[t].exports}for(var d="function"==typeof require&&require,e=0;e
"),i("table.docutils.footnote").wrap("
"),i("table.docutils.citation").wrap("
"),i(".wy-menu-vertical ul").not(".simple").siblings("a").each(function(){var t=i(this);expand=i(''),expand.on("click",function(e){return n.toggleCurrent(t),e.stopPropagation(),!1}),t.prepend(expand)})},reset:function(){var e=encodeURI(window.location.hash)||"#";try{var t=$(".wy-menu-vertical"),i=t.find('[href="'+e+'"]');if(0===i.length){var n=$('.document [id="'+e.substring(1)+'"]').closest("div.section");0===(i=t.find('[href="#'+n.attr("id")+'"]')).length&&(i=t.find('[href="#"]'))}0this.docHeight||(this.navBar.scrollTop(i),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){var t=e.closest("li");t.siblings("li.current").removeClass("current"),t.siblings().find("li.current").removeClass("current"),t.find("> ul li.current").removeClass("current"),t.toggleClass("current")}},"undefined"!=typeof window&&(window.SphinxRtdTheme={Navigation:t.exports.ThemeNav,StickyNav:t.exports.ThemeNav}),function(){for(var o=0,e=["ms","moz","webkit","o"],t=0;t/g,u=/"/g,h=/"/g,p=/&#([a-zA-Z0-9]*);?/gim,f=/:?/gim,g=/&newline;?/gim,m=/((j\s*a\s*v\s*a|v\s*b|l\s*i\s*v\s*e)\s*s\s*c\s*r\s*i\s*p\s*t\s*|m\s*o\s*c\s*h\s*a)\:/gi,v=/e\s*x\s*p\s*r\s*e\s*s\s*s\s*i\s*o\s*n\s*\(.*/gi,b=/u\s*r\s*l\s*\(.*/gi;function w(e){return e.replace(u,""")}function y(e){return e.replace(h,'"')}function _(e){return e.replace(p,function(e,t){return"x"===t[0]||"X"===t[0]?String.fromCharCode(parseInt(t.substr(1),16)):String.fromCharCode(parseInt(t,10))})}function x(e){return e.replace(f,":").replace(g," ")}function k(e){for(var t="",i=0,n=e.length;i/g;i.whiteList={a:["target","href","title"],abbr:["title"],address:[],area:["shape","coords","href","alt"],article:[],aside:[],audio:["autoplay","controls","loop","preload","src"],b:[],bdi:["dir"],bdo:["dir"],big:[],blockquote:["cite"],br:[],caption:[],center:[],cite:[],code:[],col:["align","valign","span","width"],colgroup:["align","valign","span","width"],dd:[],del:["datetime"],details:["open"],div:[],dl:[],dt:[],em:[],font:["color","size","face"],footer:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],header:[],hr:[],i:[],img:["src","alt","title","width","height"],ins:["datetime"],li:[],mark:[],nav:[],ol:[],p:[],pre:[],s:[],section:[],small:[],span:[],sub:[],sup:[],strong:[],table:["width","border","align","valign"],tbody:["align","valign"],td:["width","rowspan","colspan","align","valign"],tfoot:["align","valign"],th:["width","rowspan","colspan","align","valign"],thead:["align","valign"],tr:["rowspan","align","valign"],tt:[],u:[],ul:[],video:["autoplay","controls","loop","preload","src","height","width"]},i.getDefaultWhiteList=o,i.onTag=function(e,t,i){},i.onIgnoreTag=function(e,t,i){},i.onTagAttr=function(e,t,i){},i.onIgnoreTagAttr=function(e,t,i){},i.safeAttrValue=function(e,t,i,n){if(i=T(i),"href"===t||"src"===t){if("#"===(i=d.trim(i)))return"#";if("http://"!==i.substr(0,7)&&"https://"!==i.substr(0,8)&&"mailto:"!==i.substr(0,7)&&"tel:"!==i.substr(0,4)&&"#"!==i[0]&&"/"!==i[0])return""}else if("background"===t){if(m.lastIndex=0,m.test(i))return""}else if("style"===t){if(v.lastIndex=0,v.test(i))return"";if(b.lastIndex=0,b.test(i)&&(m.lastIndex=0,m.test(i)))return"";!1!==n&&(i=(n=n||a).process(i))}return i=E(i)},i.escapeHtml=s,i.escapeQuote=w,i.unescapeQuote=y,i.escapeHtmlEntities=_,i.escapeDangerHtml5Entities=x,i.clearNonPrintableCharacter=k,i.friendlyAttrValue=T,i.escapeAttrValue=E,i.onIgnoreTagStripAll=function(){return""},i.StripTagBody=function(o,a){"function"!=typeof a&&(a=function(){});var s=!Array.isArray(o),l=[],c=!1;return{onIgnoreTag:function(e,t,i){if(function(e){return s||-1!==d.indexOf(o,e)}(e)){if(i.isClosing){var n="[/removed]",r=i.position+n.length;return l.push([!1!==c?c:i.position,r]),c=!1,n}return c=c||i.position,"[removed]"}return a(e,t,i)},remove:function(t){var i="",n=0;return d.forEach(l,function(e){i+=t.slice(n,e[0]),n=e[1]}),i+=t.slice(n)}}},i.stripCommentTag=function(e){return e.replace(S,"")},i.stripBlankChar=function(e){var t=e.split("");return(t=t.filter(function(e){var t=e.charCodeAt(0);return 127!==t&&(!(t<=31)||(10===t||13===t))})).join("")},i.cssFilter=a,i.getDefaultCSSWhiteList=r},{"./util":5,cssfilter:9}],3:[function(e,t,i){var n=e("./default"),r=e("./parser"),o=e("./xss");for(var a in(i=t.exports=function(e,t){return new o(t).process(e)}).FilterXSS=o,n)i[a]=n[a];for(var a in r)i[a]=r[a];"undefined"!=typeof window&&(window.filterXSS=t.exports)},{"./default":2,"./parser":4,"./xss":6}],4:[function(e,t,i){var d=e("./util");function h(e){var t=d.spaceIndex(e);if(-1===t)var i=e.slice(1,-1);else i=e.slice(1,t+1);return"/"===(i=d.trim(i).toLowerCase()).slice(0,1)&&(i=i.slice(1)),"/"===i.slice(-1)&&(i=i.slice(0,-1)),i}var u=/[^a-zA-Z0-9_:\.\-]/gim;function p(e,t){for(;t"===u){n+=i(e.slice(r,o)),d=h(c=e.slice(o,s+1)),n+=t(o,n.length,d,c,"";var s=function(e){var t=w.spaceIndex(e);if(-1===t)return{html:"",closing:"/"===e[e.length-2]};var i="/"===(e=w.trim(e.slice(t+1,-1)))[e.length-1];return i&&(e=w.trim(e.slice(0,-1))),{html:e,closing:i}}(i),l=d[r],c=b(s.html,function(e,t){var i,n=-1!==w.indexOf(l,e);return y(i=p(r,e,t,n))?n?(t=g(r,e,t,v))?e+'="'+t+'"':e:y(i=f(r,e,t,n))?void 0:i:i});i="<"+r;return c&&(i+=" "+c),s.closing&&(i+=" /"),i+=">"}return y(o=h(r,i,a))?m(i):o},m);return i&&(n=i.remove(n)),n},t.exports=s},{"./default":2,"./parser":4,"./util":5,cssfilter:9}],7:[function(e,t,i){var n=e("./default"),r=e("./parser");e("./util");function h(e){return null==e}function o(e){(e=e||{}).whiteList=e.whiteList||n.whiteList,e.onAttr=e.onAttr||n.onAttr,e.onIgnoreAttr=e.onIgnoreAttr||n.onIgnoreAttr,this.options=e}o.prototype.process=function(e){if(!(e=(e=e||"").toString()))return"";var t=this.options,c=t.whiteList,d=t.onAttr,u=t.onIgnoreAttr;return r(e,function(e,t,i,n,r){var o=c[i],a=!1;!0===o?a=o:"function"==typeof o?a=o(n):o instanceof RegExp&&(a=o.test(n)),!0!==a&&(a=!1);var s,l={position:t,sourcePosition:e,source:r,isWhite:a};return a?h(s=d(i,n,l))?i+":"+n:s:h(s=u(i,n,l))?void 0:s})},t.exports=o},{"./default":8,"./parser":10,"./util":11}],8:[function(e,t,i){function n(){var e={"align-content":!1,"align-items":!1,"align-self":!1,"alignment-adjust":!1,"alignment-baseline":!1,all:!1,"anchor-point":!1,animation:!1,"animation-delay":!1,"animation-direction":!1,"animation-duration":!1,"animation-fill-mode":!1,"animation-iteration-count":!1,"animation-name":!1,"animation-play-state":!1,"animation-timing-function":!1,azimuth:!1,"backface-visibility":!1,background:!0,"background-attachment":!0,"background-clip":!0,"background-color":!0,"background-image":!0,"background-origin":!0,"background-position":!0,"background-repeat":!0,"background-size":!0,"baseline-shift":!1,binding:!1,bleed:!1,"bookmark-label":!1,"bookmark-level":!1,"bookmark-state":!1,border:!0,"border-bottom":!0,"border-bottom-color":!0,"border-bottom-left-radius":!0,"border-bottom-right-radius":!0,"border-bottom-style":!0,"border-bottom-width":!0,"border-collapse":!0,"border-color":!0,"border-image":!0,"border-image-outset":!0,"border-image-repeat":!0,"border-image-slice":!0,"border-image-source":!0,"border-image-width":!0,"border-left":!0,"border-left-color":!0,"border-left-style":!0,"border-left-width":!0,"border-radius":!0,"border-right":!0,"border-right-color":!0,"border-right-style":!0,"border-right-width":!0,"border-spacing":!0,"border-style":!0,"border-top":!0,"border-top-color":!0,"border-top-left-radius":!0,"border-top-right-radius":!0,"border-top-style":!0,"border-top-width":!0,"border-width":!0,bottom:!1,"box-decoration-break":!0,"box-shadow":!0,"box-sizing":!0,"box-snap":!0,"box-suppress":!0,"break-after":!0,"break-before":!0,"break-inside":!0,"caption-side":!1,chains:!1,clear:!0,clip:!1,"clip-path":!1,"clip-rule":!1,color:!0,"color-interpolation-filters":!0,"column-count":!1,"column-fill":!1,"column-gap":!1,"column-rule":!1,"column-rule-color":!1,"column-rule-style":!1,"column-rule-width":!1,"column-span":!1,"column-width":!1,columns:!1,contain:!1,content:!1,"counter-increment":!1,"counter-reset":!1,"counter-set":!1,crop:!1,cue:!1,"cue-after":!1,"cue-before":!1,cursor:!1,direction:!1,display:!0,"display-inside":!0,"display-list":!0,"display-outside":!0,"dominant-baseline":!1,elevation:!1,"empty-cells":!1,filter:!1,flex:!1,"flex-basis":!1,"flex-direction":!1,"flex-flow":!1,"flex-grow":!1,"flex-shrink":!1,"flex-wrap":!1,float:!1,"float-offset":!1,"flood-color":!1,"flood-opacity":!1,"flow-from":!1,"flow-into":!1,font:!0,"font-family":!0,"font-feature-settings":!0,"font-kerning":!0,"font-language-override":!0,"font-size":!0,"font-size-adjust":!0,"font-stretch":!0,"font-style":!0,"font-synthesis":!0,"font-variant":!0,"font-variant-alternates":!0,"font-variant-caps":!0,"font-variant-east-asian":!0,"font-variant-ligatures":!0,"font-variant-numeric":!0,"font-variant-position":!0,"font-weight":!0,grid:!1,"grid-area":!1,"grid-auto-columns":!1,"grid-auto-flow":!1,"grid-auto-rows":!1,"grid-column":!1,"grid-column-end":!1,"grid-column-start":!1,"grid-row":!1,"grid-row-end":!1,"grid-row-start":!1,"grid-template":!1,"grid-template-areas":!1,"grid-template-columns":!1,"grid-template-rows":!1,"hanging-punctuation":!1,height:!0,hyphens:!1,icon:!1,"image-orientation":!1,"image-resolution":!1,"ime-mode":!1,"initial-letters":!1,"inline-box-align":!1,"justify-content":!1,"justify-items":!1,"justify-self":!1,left:!1,"letter-spacing":!0,"lighting-color":!0,"line-box-contain":!1,"line-break":!1,"line-grid":!1,"line-height":!1,"line-snap":!1,"line-stacking":!1,"line-stacking-ruby":!1,"line-stacking-shift":!1,"line-stacking-strategy":!1,"list-style":!0,"list-style-image":!0,"list-style-position":!0,"list-style-type":!0,margin:!0,"margin-bottom":!0,"margin-left":!0,"margin-right":!0,"margin-top":!0,"marker-offset":!1,"marker-side":!1,marks:!1,mask:!1,"mask-box":!1,"mask-box-outset":!1,"mask-box-repeat":!1,"mask-box-slice":!1,"mask-box-source":!1,"mask-box-width":!1,"mask-clip":!1,"mask-image":!1,"mask-origin":!1,"mask-position":!1,"mask-repeat":!1,"mask-size":!1,"mask-source-type":!1,"mask-type":!1,"max-height":!0,"max-lines":!1,"max-width":!0,"min-height":!0,"min-width":!0,"move-to":!1,"nav-down":!1,"nav-index":!1,"nav-left":!1,"nav-right":!1,"nav-up":!1,"object-fit":!1,"object-position":!1,opacity:!1,order:!1,orphans:!1,outline:!1,"outline-color":!1,"outline-offset":!1,"outline-style":!1,"outline-width":!1,overflow:!1,"overflow-wrap":!1,"overflow-x":!1,"overflow-y":!1,padding:!0,"padding-bottom":!0,"padding-left":!0,"padding-right":!0,"padding-top":!0,page:!1,"page-break-after":!1,"page-break-before":!1,"page-break-inside":!1,"page-policy":!1,pause:!1,"pause-after":!1,"pause-before":!1,perspective:!1,"perspective-origin":!1,pitch:!1,"pitch-range":!1,"play-during":!1,position:!1,"presentation-level":!1,quotes:!1,"region-fragment":!1,resize:!1,rest:!1,"rest-after":!1,"rest-before":!1,richness:!1,right:!1,rotation:!1,"rotation-point":!1,"ruby-align":!1,"ruby-merge":!1,"ruby-position":!1,"shape-image-threshold":!1,"shape-outside":!1,"shape-margin":!1,size:!1,speak:!1,"speak-as":!1,"speak-header":!1,"speak-numeral":!1,"speak-punctuation":!1,"speech-rate":!1,stress:!1,"string-set":!1,"tab-size":!1,"table-layout":!1,"text-align":!0,"text-align-last":!0,"text-combine-upright":!0,"text-decoration":!0,"text-decoration-color":!0,"text-decoration-line":!0,"text-decoration-skip":!0,"text-decoration-style":!0,"text-emphasis":!0,"text-emphasis-color":!0,"text-emphasis-position":!0,"text-emphasis-style":!0,"text-height":!0,"text-indent":!0,"text-justify":!0,"text-orientation":!0,"text-overflow":!0,"text-shadow":!0,"text-space-collapse":!0,"text-transform":!0,"text-underline-position":!0,"text-wrap":!0,top:!1,transform:!1,"transform-origin":!1,"transform-style":!1,transition:!1,"transition-delay":!1,"transition-duration":!1,"transition-property":!1,"transition-timing-function":!1,"unicode-bidi":!1,"vertical-align":!1,visibility:!1,"voice-balance":!1,"voice-duration":!1,"voice-family":!1,"voice-pitch":!1,"voice-range":!1,"voice-rate":!1,"voice-stress":!1,"voice-volume":!1,volume:!1,"white-space":!1,widows:!1,width:!0,"will-change":!1,"word-break":!0,"word-spacing":!0,"word-wrap":!0,"wrap-flow":!1,"wrap-through":!1,"writing-mode":!1,"z-index":!1};return e}i.whiteList=n(),i.getDefaultWhiteList=n,i.onAttr=function(e,t,i){},i.onIgnoreAttr=function(e,t,i){}},{}],9:[function(e,t,i){var n=e("./default"),r=e("./css");for(var o in(i=t.exports=function(e,t){return new r(t).process(e)}).FilterCSS=r,n)i[o]=n[o];"undefined"!=typeof window&&(window.filterCSS=t.exports)},{"./css":7,"./default":8}],10:[function(e,t,i){var u=e("./util");t.exports=function(o,a){";"!==(o=u.trimRight(o))[o.length-1]&&(o+=";");var e=o.length,s=!1,l=0,c=0,d="";function t(){if(!s){var e=u.trim(o.slice(l,c)),t=e.indexOf(":");if(-1!==t){var i=u.trim(e.slice(0,t)),n=u.trim(e.slice(t+1));if(i){var r=a(l,d.length,i,n,e);r&&(d+=r+"; ")}}}l=c+1}for(;c");if(a.append($("

").append($("",{href:r.path,text:r.title}))),r.project!==w){var s="(from project "+r.project+")";a.append($("",{text:s}))}for(var l=0;lR&&(h=h.substr(0,R)+" ...");var p=[h];if(c.highlights.title.length&&(u=c.highlights.title[0]),c.highlights.content.length){var f=c.highlights.content;p=[];for(var g=0;g/g,"").replace(/<\/span>/g,""),a.append($("

").append($("",{href:d}).html(u)));for(var m=0;m/g,"").replace(/<\/span>/g,""),a.append($("

").html(v))}i.append(a)}}}}else console.log("Read the Docs search returned 0 result. Falling back to MkDocs search."),b()}).fail(function(e){console.log("Read the Docs search failed. Falling back to MkDocs search."),b()}),$.ajax({url:t.href,crossDomain:!0,xhrFields:{withCredentials:!0},complete:function(e,t){return"success"!==t||void 0===e.responseJSON||0===e.responseJSON.count?n.reject():n.resolve(e.responseJSON)}}).fail(function(e,t,i){return n.reject()})}function e(){var e=document.getElementById("mkdocs-search-query");e&&e.addEventListener("keyup",n);var t=window.getSearchTermFromLocation();t&&(e.value=t,n())}var w=i.project,r=i.version,o=i.language||"en";$(document).ready(function(){window.doSearchFallback=window.doSearch,window.doSearch=n,(window.initSearch=e)()})}t.exports={init:function(){var e=n.get();e.is_sphinx_builder()?function(t){var A=t.project,i=t.version,r=t.language||"en";if("undefined"!=typeof Search&&A&&i)if(t.features&&t.features.docsearch_disabled)console.log("Server side search is disabled.");else{var e=Search.query;Search.query_fallback=e,Search.query=function(S){var n=$.Deferred(),e=document.createElement("a");e.href=t.proxied_api_host+"/api/v2/search/",e.search="?q="+$.urlencode(S)+"&project="+A+"&version="+i+"&language="+r,n.then(function(e){var t=e.results||[];if(t.length){for(var i=0;i'),a=n.title;n.highlights.title.length&&(a=C(n.highlights.title[0]));var s=n.path+"?highlight="+$.urlencode(S),l=$("",{href:s});if(l.html(a),l.find("span").addClass("highlighted"),o.append(l),n.project!==A){var c=" (from project "+n.project+")",d=$("",{text:c});o.append(d)}for(var u=0;u');if("section"===h.type){var f=h,g=f.title,m=s+"#"+f.id,v=[f.content.substr(0,R)+" ..."];if(f.highlights.title.length&&(g=C(f.highlights.title[0])),f.highlights.content.length){var b=f.highlights.content;v=[];for(var w=0;w<%= section_subtitle %>

<% for (var i = 0; i < section_content.length; ++i) { %>
<%= section_content[i] %>
<% } %>',{section_subtitle_link:m,section_subtitle:g,section_content:v})}if("domain"===h.type){var y=h,x=y.role,k=s+"#"+y.id,T=y.name,E="";""!==y.content&&(E=y.content.substr(0,R)+" ..."),y.highlights.content.length&&(E="... "+C(y.highlights.content[0])+" ..."),y.highlights.name.length&&(T=C(y.highlights.name[0])),I(p,'
<%= domain_content %>
',{domain_subtitle_link:k,domain_subtitle:"["+x+"]: "+T,domain_content:E})}p.find("span").addClass("highlighted"),o.append(p),u
"))}Search.output.append(o),o.slideDown(5)}Search.status.text(_("Search finished, found %s page(s) matching the search query.").replace("%s",t.length))}else console.log("Read the Docs search failed. Falling back to Sphinx search."),Search.query_fallback(S)}).fail(function(e){console.debug("Read the Docs search failed. Falling back to Sphinx search."),Search.query_fallback(S)}).always(function(){$("#search-progress").empty(),Search.stopPulse(),Search.title.text(_("Search Results")),Search.status.fadeIn(500)}),$.ajax({url:e.href,crossDomain:!0,xhrFields:{withCredentials:!0},complete:function(e,t){return"success"!==t||void 0===e.responseJSON||0===e.responseJSON.count?n.reject():n.resolve(e.responseJSON)}}).fail(function(e,t,i){return n.reject()})}}$(document).ready(function(){"undefined"!=typeof Search&&Search.init()})}(e):e.features&&!e.features.docsearch_disabled?r(e):console.log("Server side search is disabled.")}}},{"./../../../../../../bower_components/xss/lib/index":3,"./rtd-data":14}],16:[function(r,e,t){var o=r("./rtd-data");e.exports={init:function(){var e=o.get();if($(document).on("click","[data-toggle='rst-current-version']",function(){var e=$("[data-toggle='rst-versions']").hasClass("shift-up")?"was_open":"was_closed";"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){var t=r("./../../../../../../bower_components/sphinx-rtd-theme/js/theme.js").ThemeNav;if($(document).ready(function(){setTimeout(function(){t.navBar||t.enable()},1e3)}),e.is_rtd_like_theme())if(!$("div.wy-side-scroll:first").length){console.log("Applying theme sidebar fix...");var i=$("nav.wy-nav-side:first"),n=$("
").addClass("wy-side-scroll");i.children().detach().appendTo(n),n.prependTo(i),t.navBar=n}}}}},{"./../../../../../../bower_components/sphinx-rtd-theme/js/theme.js":1,"./rtd-data":14}],17:[function(e,t,i){e("./constants");var r,n=e("./rtd-data"),o="[data-ea-publisher]",a="#ethical-ad-placement";function s(){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 l(){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 c(e){var t=null;e&&(t=e.attr("class","keep-us-sustainable"),$("

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

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

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

").appendTo(n)).offset())||i.top-$(window).scrollTop()+200>$(window).height())&&(r.is_rtd_like_theme()?(n=$("
").insertAfter("footer hr"),e="ethical-rtd"):r.is_alabaster_like_theme()&&(n="div.bodywrapper .body",e="ethical-alabaster")),t.remove(),$("
").attr("id","rtd-sidebar").attr("data-ea-publisher","readthedocs").attr("data-ea-type","readthedocs-sidebar").attr("data-ea-manual","true").addClass(e).appendTo(n)):null)}(),function(){var e=document.createElement("script");e.src="https://media.ethicalads.io/media/client/beta/ethicalads.min.js",e.type="text/javascript",e.async=!0,e.id="ethicaladsjs",document.getElementsByTagName("head")[0].appendChild(e)}(),$.ajax({url:r.api_host+"/api/v2/sustainability/data/",crossDomain:!0,xhrFields:{withCredentials:!0},dataType:"jsonp",data:{format:"jsonp",project:r.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():!r.ad_free&&s()?(l(),c(t)):$("#ethicaladsjs").on("load",function(){"undefined"!=typeof ethicalads&ðicalads.load()}))},error:function(){console.error("Error loading Read the Docs user and project information"),!r.ad_free&&s()&&(l(),c(t))}}))}}},{"./constants":12,"./rtd-data":14}],18:[function(e,t,i){var o=e("./rtd-data");t.exports={init:function(e){var t=o.get();if(!e.is_highest){var i=window.location.pathname.replace(t.version,e.slug),n=$('

Note

You are not reading the most recent version of this documentation. is the latest version available.

');n.find("a").attr("href",i).text(e.slug);var r=$("div.body");r.length||(r=$("div.document")),r.prepend(n)}}}},{"./rtd-data":14}],19:[function(e,t,i){var n,r=e("./doc-embed/sponsorship"),o=e("./doc-embed/footer.js"),a=(e("./doc-embed/rtd-data"),e("./doc-embed/sphinx")),s=e("./doc-embed/search");n=function(){o.init(),a.init(),s.init(),r.init()},"complete"===document.readyState||"interactive"===document.readyState?setTimeout(n,1):document.addEventListener("DOMContentLoaded",n)},{"./doc-embed/footer.js":13,"./doc-embed/rtd-data":14,"./doc-embed/search":15,"./doc-embed/sphinx":16,"./doc-embed/sponsorship":17}]},{},[19]); \ No newline at end of file diff --git a/readthedocs/rtd_tests/tests/test_footer.py b/readthedocs/rtd_tests/tests/test_footer.py index 0d6bdb78e2d..3f1fd00cbc5 100644 --- a/readthedocs/rtd_tests/tests/test_footer.py +++ b/readthedocs/rtd_tests/tests/test_footer.py @@ -427,7 +427,7 @@ def test_highest_version_without_tags(self): class TestFooterPerformance(TestCase): # The expected number of queries for generating the footer # This shouldn't increase unless we modify the footer API - EXPECTED_QUERIES = 18 + EXPECTED_QUERIES = 14 def setUp(self): self.pip = get( From c4a75b6527aa71bfbb47ac319a6e0d0fab9a1a51 Mon Sep 17 00:00:00 2001 From: Santos Gallegos Date: Thu, 10 Dec 2020 13:26:48 -0500 Subject: [PATCH 2/2] Apply suggestions from code review Co-authored-by: Eric Holscher <25510+ericholscher@users.noreply.github.com> --- readthedocs/analytics/proxied_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readthedocs/analytics/proxied_api.py b/readthedocs/analytics/proxied_api.py index 80cb629c59b..13b3268bba7 100644 --- a/readthedocs/analytics/proxied_api.py +++ b/readthedocs/analytics/proxied_api.py @@ -1,4 +1,4 @@ -"""Analytics views that are severd from the same domain as the docs.""" +"""Analytics views that are served from the same domain as the docs.""" from django.db.models import F from django.shortcuts import get_object_or_404