From f9ce9ca6fdcd1d04f1b7c07af19c04429c0f61f1 Mon Sep 17 00:00:00 2001 From: Santos Gallegos Date: Tue, 14 Jul 2020 19:35:58 -0500 Subject: [PATCH 1/8] Page views: use origin URL instead of page name Page name is a sphinx only concept, so we don't have correct data for page views from mkdocs projects. Using the origin url + the unresolver gives us the correct path. Also, we are always storing the full path (index.html instead of "/"), so we don't have duplicates (let me know if we do want to have duplicates). --- readthedocs/analytics/signals.py | 33 ++++++++----- readthedocs/analytics/tests.py | 48 ++++++++++--------- readthedocs/api/v2/views/footer_views.py | 16 ++++++- .../static-src/core/js/doc-embed/footer.js | 1 + .../static/core/js/readthedocs-doc-embed.js | 2 +- 5 files changed, 62 insertions(+), 38 deletions(-) diff --git a/readthedocs/analytics/signals.py b/readthedocs/analytics/signals.py index 097a5125606..4699df9d612 100644 --- a/readthedocs/analytics/signals.py +++ b/readthedocs/analytics/signals.py @@ -4,6 +4,7 @@ from django.utils import timezone from readthedocs.api.v2.signals import footer_response +from readthedocs.core.unresolver import unresolve from readthedocs.projects.models import Feature from .models import PageView @@ -17,16 +18,22 @@ def increase_page_view_count(sender, **kwargs): project = context['project'] version = context['version'] - # footer_response sends an empty path for the index - path = context['path'] or '/' - - if project.has_feature(Feature.STORE_PAGEVIEWS): - page_view, _ = PageView.objects.get_or_create( - project=project, - version=version, - path=path, - date=timezone.now().date(), - ) - PageView.objects.filter(pk=page_view.pk).update( - view_count=F('view_count') + 1 - ) + origin = kwargs['origin'] + + if not origin or not project.has_feature(Feature.STORE_PAGEVIEWS): + return + + unresolved = unresolve(origin) + path = unresolved.filename + if path.endswith('/'): + path += 'index.html' + + page_view, _ = PageView.objects.get_or_create( + project=project, + version=version, + path=path, + date=timezone.now().date(), + ) + PageView.objects.filter(pk=page_view.pk).update( + view_count=F('view_count') + 1 + ) diff --git a/readthedocs/analytics/tests.py b/readthedocs/analytics/tests.py index e8eae5c5fe1..86a1e1ec067 100644 --- a/readthedocs/analytics/tests.py +++ b/readthedocs/analytics/tests.py @@ -1,19 +1,16 @@ from unittest import mock -from django_dynamic_fixture import get -from django.test import TestCase, RequestFactory +import pytest +from django.test import RequestFactory, TestCase, override_settings from django.utils import timezone +from django_dynamic_fixture import get from readthedocs.builds.models import Version -from readthedocs.projects.models import Project, Feature +from readthedocs.projects.models import Feature, Project from .models import PageView from .signals import increase_page_view_count -from .utils import ( - anonymize_ip_address, - anonymize_user_agent, - get_client_ip, -) +from .utils import anonymize_ip_address, anonymize_user_agent, get_client_ip class UtilsTests(TestCase): @@ -97,14 +94,17 @@ def test_get_client_ip_with_remote_addr(self): self.assertEqual(client_ip, '203.0.113.195') -class AnalyticsTasksTests(TestCase): +@pytest.mark.proxito +@override_settings(PUBLIC_DOMAIN='readthedocs.io') +class AnalyticsPageViewsTests(TestCase): + def test_increase_page_view_count(self): project = get( Project, slug='project-1', ) version = get(Version, slug='1.8', project=project) - path = "index" + origin = f"https://{project.slug}.readthedocs.io/en/latest/index.html" today = timezone.now() tomorrow = timezone.now() + timezone.timedelta(days=1) @@ -117,11 +117,10 @@ def test_increase_page_view_count(self): context = { "project": project, "version": version, - "path": path, } # Without the feature flag, no PageView is created - increase_page_view_count(None, context=context) + increase_page_view_count(None, context=context, origin=origin) assert ( PageView.objects.all().count() == 0 ) @@ -135,44 +134,47 @@ def test_increase_page_view_count(self): with mock.patch('readthedocs.analytics.tasks.timezone.now') as mocked_timezone: mocked_timezone.return_value = yesterday - increase_page_view_count(None, context=context) + increase_page_view_count(None, context=context, origin=origin) assert ( PageView.objects.all().count() == 1 - ), f'PageView object for path \'{path}\' is created' + ), f'PageView object for path \'{origin}\' is created' assert ( PageView.objects.all().first().view_count == 1 ), '\'index\' has 1 view' - increase_page_view_count(None, context=context) + increase_page_view_count(None, context=context, origin=origin) assert ( PageView.objects.all().count() == 1 - ), f'PageView object for path \'{path}\' is already created' + ), f'PageView object for path \'{origin}\' is already created' + assert PageView.objects.filter(path='index.html').count() == 1 assert ( PageView.objects.all().first().view_count == 2 - ), f'\'{path}\' has 2 views now' + ), f'\'{origin}\' has 2 views now' # testing for today with mock.patch('readthedocs.analytics.tasks.timezone.now') as mocked_timezone: mocked_timezone.return_value = today - increase_page_view_count(None, context=context) + increase_page_view_count(None, context=context, origin=origin) assert ( PageView.objects.all().count() == 2 - ), f'PageView object for path \'{path}\' is created for two days (yesterday and today)' + ), f'PageView object for path \'{origin}\' is created for two days (yesterday and today)' + assert PageView.objects.filter(path='index.html').count() == 2 assert ( PageView.objects.all().order_by('-date').first().view_count == 1 - ), f'\'{path}\' has 1 view today' + ), f'\'{origin}\' has 1 view today' # testing for tomorrow with mock.patch('readthedocs.analytics.tasks.timezone.now') as mocked_timezone: mocked_timezone.return_value = tomorrow - increase_page_view_count(None, context=context) + increase_page_view_count(None, context=context, origin=origin) assert ( PageView.objects.all().count() == 3 - ), f'PageView object for path \'{path}\' is created for three days (yesterday, today & tomorrow)' + ), f'PageView object for path \'{origin}\' is created for three days (yesterday, today & tomorrow)' + assert PageView.objects.filter(path='index.html').count() == 3 assert ( PageView.objects.all().order_by('-date').first().view_count == 1 - ), f'\'{path}\' has 1 view tomorrow' + ), f'\'{origin}\' has 1 view tomorrow' diff --git a/readthedocs/api/v2/views/footer_views.py b/readthedocs/api/v2/views/footer_views.py index 68036ea0893..76b72d480b1 100644 --- a/readthedocs/api/v2/views/footer_views.py +++ b/readthedocs/api/v2/views/footer_views.py @@ -16,7 +16,7 @@ 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, Feature +from readthedocs.projects.models import Feature, Project from readthedocs.projects.version_handling import ( highest_version, parse_version_failsafe, @@ -81,6 +81,19 @@ class BaseFooterHTML(APIView): """ Render and return footer markup. + Query parameters: + + - project + - version + - page: Sphinx's page name, used for path operations, + like change between languages (deprecated in favor of ``origin``). + - origin: Full path with domain, used for path operations. + - 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` @@ -229,6 +242,7 @@ def get(self, request, format=None): request=request, context=context, resp_data=resp_data, + origin=self.request.GET.get('origin'), ) 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 6cd7dfe5783..cc734cc2183 100644 --- a/readthedocs/core/static-src/core/js/doc-embed/footer.js +++ b/readthedocs/core/static-src/core/js/doc-embed/footer.js @@ -44,6 +44,7 @@ function init() { project: rtd['project'], version: rtd['version'], page: rtd['page'], + origin: window.location.href, theme: rtd.get_theme_name(), format: "jsonp", }; diff --git a/readthedocs/core/static/core/js/readthedocs-doc-embed.js b/readthedocs/core/static/core/js/readthedocs-doc-embed.js index 9fda423f0f3..8a398f7487c 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(s,a,l){function c(t,e){if(!a[t]){if(!s[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=a[t]={exports:{}};s[t][0].call(r.exports,function(e){return c(s[t][1][e]||e)},r,r.exports,o,s,a,l)}return a[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,w=/u\s*r\s*l\s*\(.*/gi;function b(e){return e.replace(u,""")}function _(e){return e.replace(h,'"')}function y(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 k(e){return e.replace(f,":").replace(g," ")}function x(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(w.lastIndex=0,w.test(i)&&(m.lastIndex=0,m.test(i)))return"";!1!==n&&(i=(n=n||s).process(i))}return i=E(i)},i.escapeHtml=a,i.escapeQuote=b,i.unescapeQuote=_,i.escapeHtmlEntities=y,i.escapeDangerHtml5Entities=k,i.clearNonPrintableCharacter=x,i.friendlyAttrValue=T,i.escapeAttrValue=E,i.onIgnoreTagStripAll=function(){return""},i.StripTagBody=function(o,s){"function"!=typeof s&&(s=function(){});var a=!Array.isArray(o),l=[],c=!1;return{onIgnoreTag:function(e,t,i){if(function(e){return a||-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 s(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=s,i.getDefaultCSSWhiteList=r},{"./util":5,cssfilter:10}],3:[function(e,t,i){var n=e("./default"),r=e("./parser"),o=e("./xss");for(var s in(i=t.exports=function(e,t){return new o(t).process(e)}).FilterXSS=o,n)i[s]=n[s];for(var s in r)i[s]=r[s];"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,a+1)),n+=t(o,n.length,d,c,"";var a=function(e){var t=b.spaceIndex(e);if(-1===t)return{html:"",closing:"/"===e[e.length-2]};var i="/"===(e=b.trim(e.slice(t+1,-1)))[e.length-1];return i&&(e=b.trim(e.slice(0,-1))),{html:e,closing:i}}(i),l=d[r],c=w(a.html,function(e,t){var i,n=-1!==b.indexOf(l,e);return _(i=p(r,e,t,n))?n?(t=g(r,e,t,v))?e+'="'+t+'"':e:_(i=f(r,e,t,n))?void 0:i:i});i="<"+r;return c&&(i+=" "+c),a.closing&&(i+=" /"),i+=">"}return _(o=h(r,i,s))?m(i):o},m);return i&&(n=i.remove(n)),n},t.exports=a},{"./default":2,"./parser":4,"./util":5,cssfilter:10}],7:[function(e,t,i){var n,r;n=this,r=function(){var T=!0;function s(i){function e(e){var t=i.match(e);return t&&1t[1][i])return 1;if(t[0][i]!==t[1][i])return-1;if(0===i)return 0}}function o(e,t,i){var n=a;"string"==typeof t&&(i=t,t=void 0),void 0===t&&(t=!1),i&&(n=s(i));var r=""+n.version;for(var o in e)if(e.hasOwnProperty(o)&&n[o]){if("string"!=typeof e[o])throw new Error("Browser version in the minVersion map should be a string: "+o+": "+String(e));return E([r,e[o]])<0}return t}return a.test=function(e){for(var t=0;t");if(s.append($("

").append($("",{href:r.link,text:r.title}))),r.project!==b){var a="(from project "+r.project+")";s.append($("",{text:a}))}for(var l=0;lC&&(h=h.substr(0,C)+" ...");var p=[h];if(c.highlight&&(c.highlight["sections.title"]&&(u=c.highlight["sections.title"][0]),c.highlight["sections.content"])){var f=c.highlight["sections.content"];p=[];for(var g=0;g/g,"").replace(/<\/span>/g,""),s.append($("

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

").html(v))}i.append(s)}}}}else console.log("Read the Docs search returned 0 result. Falling back to MkDocs search."),w()}).fail(function(e){console.log("Read the Docs search failed. Falling back to MkDocs search."),w()}),$.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 b=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/docsearch/",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;r&&r.title&&(a=O(r.title[0]));var l=n.link+"?highlight="+$.urlencode(S),c=$("",{href:l});if(c.html(a),c.find("span").addClass("highlighted"),s.append(c),n.project!==A){var d=" (from project "+n.project+")",u=$("",{text:d});s.append(u)}for(var h=0;h'),f="",g="",m="",v="",w="",b="",y="",k="",x="",T="";if("sections"===o[h].type){if(g=(f=o[h])._source.title,m=l+"#"+f._source.id,v=[f._source.content.substr(0,C)+" ..."],f.highlight&&(f.highlight["sections.title"]&&(g=O(f.highlight["sections.title"][0])),f.highlight["sections.content"])){w=f.highlight["sections.content"],v=[];for(var E=0;E<%= section_subtitle %><% for (var i = 0; i < section_content.length; ++i) { %>

<%= section_content[i] %>
<% } %>',{section_subtitle_link:m,section_subtitle:g,section_content:v})}"domains"===o[h].type&&(y=(b=o[h])._source.role_name,k=l+"#"+b._source.anchor,x=b._source.name,(T="")!==b._source.docstrings&&(T=b._source.docstrings.substr(0,C)+" ..."),b.highlight&&(b.highlight["domains.docstrings"]&&(T="... "+O(b.highlight["domains.docstrings"][0])+" ..."),b.highlight["domains.name"]&&(x=O(b.highlight["domains.name"][0]))),M(p,'
<%= domain_content %>
',{domain_subtitle_link:k,domain_subtitle:"["+y+"]: "+x,domain_content:T})),p.find("span").addClass("highlighted"),s.append(p),h!==o.length-1&&s.append($("
"))}Search.output.append(s),s.slideDown(5)}t.length?Search.status.text(_("Search finished, found %s page(s) matching the search query.").replace("%s",t.length)):(Search.query_fallback(S),console.log("Read the Docs search failed. Falling back to Sphinx search."))}).fail(function(e){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":16}],18:[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 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":16}],19:[function(e,t,i){var l,c=e("./constants"),d=e("./rtd-data"),n=e("bowser"),u="#ethical-ad-placement";function h(){var e,t,i="rtd-"+(Math.random()+1).toString(36).substring(4),n=c.PROMO_TYPES.LEFTNAV,r=c.DEFAULT_PROMO_PRIORITY,o=null;return l.is_mkdocs_builder()&&l.is_rtd_like_theme()?(o="nav.wy-nav-side",e="ethical-rtd ethical-dark-theme"):l.is_rtd_like_theme()?(o="nav.wy-nav-side > div.wy-side-scroll",e="ethical-rtd ethical-dark-theme"):l.is_alabaster_like_theme()&&(o="div.sphinxsidebar > div.sphinxsidebarwrapper",e="ethical-alabaster"),o?($("
").attr("id",i).addClass(e).appendTo(o),(!(t=$("#"+i).offset())||t.top>$(window).height())&&(r=c.LOW_PROMO_PRIORITY),{div_id:i,display_type:n,priority:r}):null}function p(){var e,t,i="rtd-"+(Math.random()+1).toString(36).substring(4),n=c.PROMO_TYPES.FOOTER,r=c.DEFAULT_PROMO_PRIORITY,o=null;return l.is_rtd_like_theme()?(o=$("
").insertAfter("footer hr"),e="ethical-rtd"):l.is_alabaster_like_theme()&&(o="div.bodywrapper .body",e="ethical-alabaster"),o?($("
").attr("id",i).addClass(e).appendTo(o),(!(t=$("#"+i).offset())||t.top<$(window).height())&&(r=c.LOW_PROMO_PRIORITY),{div_id:i,display_type:n,priority:r}):null}function f(){var e="rtd-"+(Math.random()+1).toString(36).substring(4),t=c.PROMO_TYPES.FIXED_FOOTER,i=c.DEFAULT_PROMO_PRIORITY;return n&&n.mobile&&(i=c.MAXIMUM_PROMO_PRIORITY),$("
").attr("id",e).appendTo("body"),{div_id:e,display_type:t,priority:i}}function g(e){this.id=e.id,this.div_id=e.div_id||"",this.html=e.html||"",this.display_type=e.display_type||"",this.view_tracking_url=e.view_url,this.click_handler=function(){"undefined"!=typeof ga?ga("rtfd.send","event","Promo","Click",e.id):"undefined"!=typeof _gaq&&_gaq.push(["rtfd._setAccount","UA-17997319-1"],["rtfd._trackEvent","Promo","Click",e.id])}}g.prototype.display=function(){var e="#"+this.div_id,t=this.view_tracking_url;$(e).html(this.html),$(e).find('a[href*="/sustainability/click/"]').on("click",this.click_handler);function i(){$.inViewport($(e),-3)&&($("").attr("src",t).css("display","none").appendTo(e),$(window).off(".rtdinview"),$(".wy-side-scroll").off(".rtdinview"))}$(window).on("DOMContentLoaded.rtdinview load.rtdinview scroll.rtdinview resize.rtdinview",i),$(".wy-side-scroll").on("scroll.rtdinview",i),$(".ethical-close").on("click",function(){return $(e).hide(),!1}),this.post_promo_display()},g.prototype.disable=function(){$("#"+this.div_id).hide()},g.prototype.post_promo_display=function(){this.display_type===c.PROMO_TYPES.FOOTER&&($("
").insertAfter("#"+this.div_id),$("
").insertBefore("#"+this.div_id+".ethical-alabaster .ethical-footer"))},t.exports={Promo:g,init:function(){var e,t,i={format:"jsonp"},n=[],r=[],o=[],s=[p,h,f];if(l=d.get(),t=function(){var e,t="rtd-"+(Math.random()+1).toString(36).substring(4),i=c.PROMO_TYPES.LEFTNAV;return e=l.is_rtd_like_theme()?"ethical-rtd ethical-dark-theme":"ethical-alabaster",0<$(u).length?($("
").attr("id",t).addClass(e).appendTo(u),{div_id:t,display_type:i}):null}())n.push(t.div_id),r.push(t.display_type),o.push(t.priority||c.DEFAULT_PROMO_PRIORITY),!0;else{if(!l.show_promo())return;for(var a=0;a").attr("id","rtd-detection").attr("class","ethical-rtd").html(" ").appendTo("body"),0===$("#rtd-detection").height()&&(e=!0),$("#rtd-detection").remove(),e}()&&(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(){var e=h(),t=null;e&&e.div_id&&(t=$("#"+e.div_id).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))}())}})}}},{"./constants":14,"./rtd-data":16,bowser:7}],20:[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":16}],21:[function(e,t,i){var n=e("./doc-embed/sponsorship"),r=e("./doc-embed/footer.js"),o=(e("./doc-embed/rtd-data"),e("./doc-embed/sphinx")),s=e("./doc-embed/search");$.extend(e("verge")),$(document).ready(function(){r.init(),o.init(),s.init(),n.init()})},{"./doc-embed/footer.js":15,"./doc-embed/rtd-data":16,"./doc-embed/search":17,"./doc-embed/sphinx":18,"./doc-embed/sponsorship":19,verge:13}]},{},[21]); \ No newline at end of file +!function o(s,a,l){function c(t,e){if(!a[t]){if(!s[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=a[t]={exports:{}};s[t][0].call(r.exports,function(e){return c(s[t][1][e]||e)},r,r.exports,o,s,a,l)}return a[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,w=/u\s*r\s*l\s*\(.*/gi;function b(e){return e.replace(u,""")}function _(e){return e.replace(h,'"')}function y(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 k(e){return e.replace(f,":").replace(g," ")}function x(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(w.lastIndex=0,w.test(i)&&(m.lastIndex=0,m.test(i)))return"";!1!==n&&(i=(n=n||s).process(i))}return i=E(i)},i.escapeHtml=a,i.escapeQuote=b,i.unescapeQuote=_,i.escapeHtmlEntities=y,i.escapeDangerHtml5Entities=k,i.clearNonPrintableCharacter=x,i.friendlyAttrValue=T,i.escapeAttrValue=E,i.onIgnoreTagStripAll=function(){return""},i.StripTagBody=function(o,s){"function"!=typeof s&&(s=function(){});var a=!Array.isArray(o),l=[],c=!1;return{onIgnoreTag:function(e,t,i){if(function(e){return a||-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 s(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=s,i.getDefaultCSSWhiteList=r},{"./util":5,cssfilter:10}],3:[function(e,t,i){var n=e("./default"),r=e("./parser"),o=e("./xss");for(var s in(i=t.exports=function(e,t){return new o(t).process(e)}).FilterXSS=o,n)i[s]=n[s];for(var s in r)i[s]=r[s];"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,a+1)),n+=t(o,n.length,d,c,"";var a=function(e){var t=b.spaceIndex(e);if(-1===t)return{html:"",closing:"/"===e[e.length-2]};var i="/"===(e=b.trim(e.slice(t+1,-1)))[e.length-1];return i&&(e=b.trim(e.slice(0,-1))),{html:e,closing:i}}(i),l=d[r],c=w(a.html,function(e,t){var i,n=-1!==b.indexOf(l,e);return _(i=p(r,e,t,n))?n?(t=g(r,e,t,v))?e+'="'+t+'"':e:_(i=f(r,e,t,n))?void 0:i:i});i="<"+r;return c&&(i+=" "+c),a.closing&&(i+=" /"),i+=">"}return _(o=h(r,i,s))?m(i):o},m);return i&&(n=i.remove(n)),n},t.exports=a},{"./default":2,"./parser":4,"./util":5,cssfilter:10}],7:[function(e,t,i){var n,r;n=this,r=function(){var T=!0;function s(i){function e(e){var t=i.match(e);return t&&1t[1][i])return 1;if(t[0][i]!==t[1][i])return-1;if(0===i)return 0}}function o(e,t,i){var n=a;"string"==typeof t&&(i=t,t=void 0),void 0===t&&(t=!1),i&&(n=s(i));var r=""+n.version;for(var o in e)if(e.hasOwnProperty(o)&&n[o]){if("string"!=typeof e[o])throw new Error("Browser version in the minVersion map should be a string: "+o+": "+String(e));return E([r,e[o]])<0}return t}return a.test=function(e){for(var t=0;t");if(s.append($("

").append($("",{href:r.link,text:r.title}))),r.project!==b){var a="(from project "+r.project+")";s.append($("",{text:a}))}for(var l=0;lC&&(h=h.substr(0,C)+" ...");var p=[h];if(c.highlight&&(c.highlight["sections.title"]&&(u=c.highlight["sections.title"][0]),c.highlight["sections.content"])){var f=c.highlight["sections.content"];p=[];for(var g=0;g/g,"").replace(/<\/span>/g,""),s.append($("

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

").html(v))}i.append(s)}}}}else console.log("Read the Docs search returned 0 result. Falling back to MkDocs search."),w()}).fail(function(e){console.log("Read the Docs search failed. Falling back to MkDocs search."),w()}),$.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 b=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/docsearch/",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;r&&r.title&&(a=O(r.title[0]));var l=n.link+"?highlight="+$.urlencode(S),c=$("",{href:l});if(c.html(a),c.find("span").addClass("highlighted"),s.append(c),n.project!==A){var d=" (from project "+n.project+")",u=$("",{text:d});s.append(u)}for(var h=0;h'),f="",g="",m="",v="",w="",b="",y="",k="",x="",T="";if("sections"===o[h].type){if(g=(f=o[h])._source.title,m=l+"#"+f._source.id,v=[f._source.content.substr(0,C)+" ..."],f.highlight&&(f.highlight["sections.title"]&&(g=O(f.highlight["sections.title"][0])),f.highlight["sections.content"])){w=f.highlight["sections.content"],v=[];for(var E=0;E<%= section_subtitle %>

<% for (var i = 0; i < section_content.length; ++i) { %>
<%= section_content[i] %>
<% } %>',{section_subtitle_link:m,section_subtitle:g,section_content:v})}"domains"===o[h].type&&(y=(b=o[h])._source.role_name,k=l+"#"+b._source.anchor,x=b._source.name,(T="")!==b._source.docstrings&&(T=b._source.docstrings.substr(0,C)+" ..."),b.highlight&&(b.highlight["domains.docstrings"]&&(T="... "+O(b.highlight["domains.docstrings"][0])+" ..."),b.highlight["domains.name"]&&(x=O(b.highlight["domains.name"][0]))),M(p,'
<%= domain_content %>
',{domain_subtitle_link:k,domain_subtitle:"["+y+"]: "+x,domain_content:T})),p.find("span").addClass("highlighted"),s.append(p),h!==o.length-1&&s.append($("
"))}Search.output.append(s),s.slideDown(5)}t.length?Search.status.text(_("Search finished, found %s page(s) matching the search query.").replace("%s",t.length)):(Search.query_fallback(S),console.log("Read the Docs search failed. Falling back to Sphinx search."))}).fail(function(e){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":16}],18:[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 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":16}],19:[function(e,t,i){var l,c=e("./constants"),d=e("./rtd-data"),n=e("bowser"),u="#ethical-ad-placement";function h(){var e,t,i="rtd-"+(Math.random()+1).toString(36).substring(4),n=c.PROMO_TYPES.LEFTNAV,r=c.DEFAULT_PROMO_PRIORITY,o=null;return l.is_mkdocs_builder()&&l.is_rtd_like_theme()?(o="nav.wy-nav-side",e="ethical-rtd ethical-dark-theme"):l.is_rtd_like_theme()?(o="nav.wy-nav-side > div.wy-side-scroll",e="ethical-rtd ethical-dark-theme"):l.is_alabaster_like_theme()&&(o="div.sphinxsidebar > div.sphinxsidebarwrapper",e="ethical-alabaster"),o?($("
").attr("id",i).addClass(e).appendTo(o),(!(t=$("#"+i).offset())||t.top>$(window).height())&&(r=c.LOW_PROMO_PRIORITY),{div_id:i,display_type:n,priority:r}):null}function p(){var e,t,i="rtd-"+(Math.random()+1).toString(36).substring(4),n=c.PROMO_TYPES.FOOTER,r=c.DEFAULT_PROMO_PRIORITY,o=null;return l.is_rtd_like_theme()?(o=$("
").insertAfter("footer hr"),e="ethical-rtd"):l.is_alabaster_like_theme()&&(o="div.bodywrapper .body",e="ethical-alabaster"),o?($("
").attr("id",i).addClass(e).appendTo(o),(!(t=$("#"+i).offset())||t.top<$(window).height())&&(r=c.LOW_PROMO_PRIORITY),{div_id:i,display_type:n,priority:r}):null}function f(){var e="rtd-"+(Math.random()+1).toString(36).substring(4),t=c.PROMO_TYPES.FIXED_FOOTER,i=c.DEFAULT_PROMO_PRIORITY;return n&&n.mobile&&(i=c.MAXIMUM_PROMO_PRIORITY),$("
").attr("id",e).appendTo("body"),{div_id:e,display_type:t,priority:i}}function g(e){this.id=e.id,this.div_id=e.div_id||"",this.html=e.html||"",this.display_type=e.display_type||"",this.view_tracking_url=e.view_url,this.click_handler=function(){"undefined"!=typeof ga?ga("rtfd.send","event","Promo","Click",e.id):"undefined"!=typeof _gaq&&_gaq.push(["rtfd._setAccount","UA-17997319-1"],["rtfd._trackEvent","Promo","Click",e.id])}}g.prototype.display=function(){var e="#"+this.div_id,t=this.view_tracking_url;$(e).html(this.html),$(e).find('a[href*="/sustainability/click/"]').on("click",this.click_handler);function i(){$.inViewport($(e),-3)&&($("").attr("src",t).css("display","none").appendTo(e),$(window).off(".rtdinview"),$(".wy-side-scroll").off(".rtdinview"))}$(window).on("DOMContentLoaded.rtdinview load.rtdinview scroll.rtdinview resize.rtdinview",i),$(".wy-side-scroll").on("scroll.rtdinview",i),$(".ethical-close").on("click",function(){return $(e).hide(),!1}),this.post_promo_display()},g.prototype.disable=function(){$("#"+this.div_id).hide()},g.prototype.post_promo_display=function(){this.display_type===c.PROMO_TYPES.FOOTER&&($("
").insertAfter("#"+this.div_id),$("
").insertBefore("#"+this.div_id+".ethical-alabaster .ethical-footer"))},t.exports={Promo:g,init:function(){var e,t,i={format:"jsonp"},n=[],r=[],o=[],s=[p,h,f];if(l=d.get(),t=function(){var e,t="rtd-"+(Math.random()+1).toString(36).substring(4),i=c.PROMO_TYPES.LEFTNAV;return e=l.is_rtd_like_theme()?"ethical-rtd ethical-dark-theme":"ethical-alabaster",0<$(u).length?($("
").attr("id",t).addClass(e).appendTo(u),{div_id:t,display_type:i}):null}())n.push(t.div_id),r.push(t.display_type),o.push(t.priority||c.DEFAULT_PROMO_PRIORITY),!0;else{if(!l.show_promo())return;for(var a=0;a").attr("id","rtd-detection").attr("class","ethical-rtd").html(" ").appendTo("body"),0===$("#rtd-detection").height()&&(e=!0),$("#rtd-detection").remove(),e}()&&(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(){var e=h(),t=null;e&&e.div_id&&(t=$("#"+e.div_id).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))}())}})}}},{"./constants":14,"./rtd-data":16,bowser:7}],20:[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":16}],21:[function(e,t,i){var n=e("./doc-embed/sponsorship"),r=e("./doc-embed/footer.js"),o=(e("./doc-embed/rtd-data"),e("./doc-embed/sphinx")),s=e("./doc-embed/search");$.extend(e("verge")),$(document).ready(function(){r.init(),o.init(),s.init(),n.init()})},{"./doc-embed/footer.js":15,"./doc-embed/rtd-data":16,"./doc-embed/search":17,"./doc-embed/sphinx":18,"./doc-embed/sponsorship":19,verge:13}]},{},[21]); \ No newline at end of file From c27e4df89913fc5672eba221584ba632813d8dbd Mon Sep 17 00:00:00 2001 From: Santos Gallegos Date: Tue, 14 Jul 2020 20:02:47 -0500 Subject: [PATCH 2/8] Fix test --- readthedocs/rtd_tests/tests/test_footer.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/readthedocs/rtd_tests/tests/test_footer.py b/readthedocs/rtd_tests/tests/test_footer.py index b1aa66dbb66..7442d422348 100644 --- a/readthedocs/rtd_tests/tests/test_footer.py +++ b/readthedocs/rtd_tests/tests/test_footer.py @@ -1,4 +1,5 @@ from unittest import mock + from django.contrib.sessions.backends.base import SessionBase from django.test import TestCase from django.test.utils import override_settings @@ -418,7 +419,7 @@ class TestFooterPerformance(APITestCase): # The expected number of queries for generating the footer # This shouldn't increase unless we modify the footer API - EXPECTED_QUERIES = 14 + EXPECTED_QUERIES = 13 def setUp(self): self.pip = Project.objects.get(slug='pip') From d2679034e4708a364577d251ade07a8c67f8ec8e Mon Sep 17 00:00:00 2001 From: Santos Gallegos Date: Wed, 15 Jul 2020 17:38:50 -0500 Subject: [PATCH 3/8] Add unresolve_from_request --- readthedocs/core/unresolver.py | 34 +++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/readthedocs/core/unresolver.py b/readthedocs/core/unresolver.py index bd5a6da9f14..9569859fcf2 100644 --- a/readthedocs/core/unresolver.py +++ b/readthedocs/core/unresolver.py @@ -1,14 +1,14 @@ import logging -from urllib.parse import urlparse from collections import namedtuple +from urllib.parse import urlparse -from django.urls import resolve as url_resolve from django.test.client import RequestFactory +from django.urls import resolve as url_resolve from readthedocs.core.utils.extend import SettingsOverrideObject from readthedocs.proxito.middleware import map_host_to_project_slug -from readthedocs.proxito.views.utils import _get_project_data_from_request from readthedocs.proxito.views.mixins import ServeDocsMixin +from readthedocs.proxito.views.utils import _get_project_data_from_request log = logging.getLogger(__name__) @@ -32,12 +32,31 @@ def unresolve(self, url): # TODO: Make this not depend on the request object, # but instead move all this logic here working on strings. request = RequestFactory().get(path=path, HTTP_HOST=domain) - project_slug = request.host_project_slug = map_host_to_project_slug(request) + project_slug = map_host_to_project_slug(request) # Handle returning a response if hasattr(project_slug, 'status_code'): return None + request.host_project_slug = request.slug = project_slug + return self.unresolve_from_request(request, path) + + def unresolve_from_request(self, request, path): + """ + Unresolve using a request. + + ``path`` can be a full URL, but the domain will be ignored, + since that information is already in the request object. + + None is returned if the request isn't valid. + """ + parsed = urlparse(path) + path = parsed.path + project_slug = getattr(request, 'slug', None) + + if not project_slug: + return None + _, __, kwargs = url_resolve( path, urlconf='readthedocs.proxito.urls', @@ -46,7 +65,7 @@ def unresolve(self, url): mixin = ServeDocsMixin() version_slug = mixin.get_version_from_host(request, kwargs.get('version_slug')) - final_project, lang_slug, version_slug, filename = _get_project_data_from_request( # noqa + final_project, lang_slug, version_slug, filename = _get_project_data_from_request( request, project_slug=project_slug, subproject_slug=kwargs.get('subproject_slug'), @@ -63,8 +82,8 @@ def unresolve(self, url): log.info( 'Unresolver parsed: ' - 'url=%s project=%s lang_slug=%s version_slug=%s filename=%s', - url, final_project.slug, lang_slug, version_slug, filename + 'project=%s lang_slug=%s version_slug=%s filename=%s', + final_project.slug, lang_slug, version_slug, filename ) return UnresolvedObject(final_project, lang_slug, version_slug, filename, parsed.fragment) @@ -76,3 +95,4 @@ class Unresolver(SettingsOverrideObject): unresolver = Unresolver() unresolve = unresolver.unresolve +unresolve_from_request = unresolver.unresolve_from_request From 70ad4b8d5f8ab8e51a953560c5dd83638edd8d64 Mon Sep 17 00:00:00 2001 From: Santos Gallegos Date: Wed, 15 Jul 2020 17:40:04 -0500 Subject: [PATCH 4/8] Use unresolve_from_request --- readthedocs/analytics/signals.py | 25 +++++++++++++----------- readthedocs/api/v2/signals.py | 2 +- readthedocs/api/v2/views/footer_views.py | 2 +- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/readthedocs/analytics/signals.py b/readthedocs/analytics/signals.py index 4699df9d612..7e7b7c441ba 100644 --- a/readthedocs/analytics/signals.py +++ b/readthedocs/analytics/signals.py @@ -4,36 +4,39 @@ from django.utils import timezone from readthedocs.api.v2.signals import footer_response -from readthedocs.core.unresolver import unresolve +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, **kwargs): +def increase_page_view_count(sender, request, context, response_data, origin, **kwargs): """Increase the page view count for the given project.""" del sender # unused - context = kwargs['context'] project = context['project'] version = context['version'] - origin = kwargs['origin'] if not origin or not project.has_feature(Feature.STORE_PAGEVIEWS): return - unresolved = unresolve(origin) + unresolved = unresolve_from_request(request, origin) + if not unresolved: + return + path = unresolved.filename - if path.endswith('/'): - path += 'index.html' - page_view, _ = PageView.objects.get_or_create( + fields = dict( project=project, version=version, path=path, date=timezone.now().date(), ) - PageView.objects.filter(pk=page_view.pk).update( - view_count=F('view_count') + 1 - ) + + 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/api/v2/signals.py b/readthedocs/api/v2/signals.py index 419e78f43f5..f0c149d2c9b 100644 --- a/readthedocs/api/v2/signals.py +++ b/readthedocs/api/v2/signals.py @@ -4,5 +4,5 @@ footer_response = django.dispatch.Signal( - providing_args=['request', 'context', 'response_data'], + providing_args=['request', 'context', 'response_data', 'origin'], ) diff --git a/readthedocs/api/v2/views/footer_views.py b/readthedocs/api/v2/views/footer_views.py index 76b72d480b1..f2cd4fdfb4d 100644 --- a/readthedocs/api/v2/views/footer_views.py +++ b/readthedocs/api/v2/views/footer_views.py @@ -241,7 +241,7 @@ def get(self, request, format=None): sender=None, request=request, context=context, - resp_data=resp_data, + response_data=resp_data, origin=self.request.GET.get('origin'), ) From 65fcc7cca5bd66436dfdb161688e12bbc1f22505 Mon Sep 17 00:00:00 2001 From: Santos Gallegos Date: Wed, 15 Jul 2020 17:40:20 -0500 Subject: [PATCH 5/8] Update tests --- readthedocs/rtd_tests/tests/test_footer.py | 83 ++++++++++++++-------- 1 file changed, 52 insertions(+), 31 deletions(-) diff --git a/readthedocs/rtd_tests/tests/test_footer.py b/readthedocs/rtd_tests/tests/test_footer.py index 7442d422348..a2e41a65210 100644 --- a/readthedocs/rtd_tests/tests/test_footer.py +++ b/readthedocs/rtd_tests/tests/test_footer.py @@ -1,21 +1,18 @@ from unittest import mock +import pytest from django.contrib.sessions.backends.base import SessionBase -from django.test import TestCase -from django.test.utils import override_settings +from django.test import TestCase, override_settings from django.urls import reverse from django_dynamic_fixture import get -from rest_framework.test import APIRequestFactory, APITestCase +from rest_framework.test import APIRequestFactory -from readthedocs.api.v2.views.footer_views import ( - FooterHTML, - get_version_compare_data, -) +from readthedocs.api.v2.views.footer_views import get_version_compare_data from readthedocs.builds.constants import BRANCH, LATEST, TAG from readthedocs.builds.models import Version from readthedocs.core.middleware import ReadTheDocsSessionMiddleware from readthedocs.projects.constants import PUBLIC -from readthedocs.projects.models import Project +from readthedocs.projects.models import Feature, Project class BaseTestFooterHTML: @@ -412,33 +409,56 @@ def test_highest_version_without_tags(self): self.assertDictEqual(valid_data, returned_data) -class TestFooterPerformance(APITestCase): - fixtures = ['test_data', 'eric'] - url = '/api/v2/footer_html/?project=pip&version=latest&page=index&docroot=/' - factory = APIRequestFactory() - +@pytest.mark.proxito +@override_settings(PUBLIC_DOMAIN='readthedocs.io') +class TestFooterPerformance(TestCase): # The expected number of queries for generating the footer # This shouldn't increase unless we modify the footer API - EXPECTED_QUERIES = 13 + EXPECTED_QUERIES = 18 def setUp(self): - self.pip = Project.objects.get(slug='pip') - self.pip.show_version_warning = True - self.pip.save() - self.pip.versions.update(built=True) + 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) - def render(self): - request = self.factory.get(self.url) - response = FooterHTML.as_view()(request) - response.render() - return response + self.url = ( + reverse('footer_html') + + f'?project={self.pip.slug}&version={self.latest.slug}&page=index&docroot=/docs/' + + '&origin=https://pip.readthedocs.io/en/latest/index.html' + ) + self.host = 'pip.readthedocs.io' + + # Run tests with all available features + # that can increase the number of queries. + feature, _ = Feature.objects.get_or_create( + feature_id=Feature.STORE_PAGEVIEWS, + ) + self.pip.feature_set.add(feature) def test_version_queries(self): - # The number of Versions shouldn't impact the number of queries with self.assertNumQueries(self.EXPECTED_QUERIES): - response = self.render() + response = self.client.get(self.url, HTTP_HOST=self.host) self.assertContains(response, '0.8.1') + # Seconde 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, HTTP_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( @@ -450,16 +470,17 @@ def test_version_queries(self): ) with self.assertNumQueries(self.EXPECTED_QUERIES): - response = self.render() + response = self.client.get(self.url, HTTP_HOST=self.host) self.assertContains(response, '0.99.0') def test_domain_queries(self): - # Setting up a custom domain shouldn't impact the number of queries + domain = 'docs.foobar.com' self.pip.domains.create( - domain='http://docs.foobar.com', + domain=f'http://{domain}', canonical=True, ) - with self.assertNumQueries(self.EXPECTED_QUERIES): - response = self.render() - self.assertContains(response, 'docs.foobar.com') + # Setting up a custom domain increases only one query. + with self.assertNumQueries(self.EXPECTED_QUERIES + 1): + response = self.client.get(self.url, HTTP_HOST=domain) + self.assertContains(response, domain) From 2e5aa58ce7b7233e6d92befafd1559ee0f6a7429 Mon Sep 17 00:00:00 2001 From: Santos Gallegos Date: Wed, 15 Jul 2020 18:06:24 -0500 Subject: [PATCH 6/8] Fix tests --- readthedocs/analytics/signals.py | 6 ++-- readthedocs/analytics/tests.py | 50 +++++++++++++++++--------------- readthedocs/core/unresolver.py | 7 ++--- 3 files changed, 34 insertions(+), 29 deletions(-) diff --git a/readthedocs/analytics/signals.py b/readthedocs/analytics/signals.py index 7e7b7c441ba..2b6fd9f4846 100644 --- a/readthedocs/analytics/signals.py +++ b/readthedocs/analytics/signals.py @@ -11,9 +11,11 @@ @receiver(footer_response) -def increase_page_view_count(sender, request, context, response_data, origin, **kwargs): +def increase_page_view_count(sender, *, request, context, origin, **kwargs): """Increase the page view count for the given project.""" - del sender # unused + # unused + del sender + del kwargs project = context['project'] version = context['version'] diff --git a/readthedocs/analytics/tests.py b/readthedocs/analytics/tests.py index 86a1e1ec067..965b97b1e3d 100644 --- a/readthedocs/analytics/tests.py +++ b/readthedocs/analytics/tests.py @@ -2,6 +2,7 @@ import pytest from django.test import RequestFactory, TestCase, override_settings +from django.urls import reverse from django.utils import timezone from django_dynamic_fixture import get @@ -9,7 +10,6 @@ from readthedocs.projects.models import Feature, Project from .models import PageView -from .signals import increase_page_view_count from .utils import anonymize_ip_address, anonymize_user_agent, get_client_ip @@ -98,29 +98,31 @@ def test_get_client_ip_with_remote_addr(self): @override_settings(PUBLIC_DOMAIN='readthedocs.io') class AnalyticsPageViewsTests(TestCase): - def test_increase_page_view_count(self): - project = get( + def setUp(self): + self.project = get( Project, - slug='project-1', + slug='pip', + ) + self.version = get(Version, slug='1.8', project=self.project) + self.origin = 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/' + + f'&origin={self.origin}' ) - version = get(Version, slug='1.8', project=project) - origin = f"https://{project.slug}.readthedocs.io/en/latest/index.html" - today = timezone.now() - tomorrow = timezone.now() + timezone.timedelta(days=1) - yesterday = timezone.now() - timezone.timedelta(days=1) + self.today = timezone.now() + self.tomorrow = timezone.now() + timezone.timedelta(days=1) + self.yesterday = timezone.now() - timezone.timedelta(days=1) + def test_increase_page_view_count(self): assert ( PageView.objects.all().count() == 0 ), 'There\'s no PageView object created yet.' - context = { - "project": project, - "version": version, - } - # Without the feature flag, no PageView is created - increase_page_view_count(None, context=context, origin=origin) + self.client.get(self.url, HTTP_HOST=self.host) assert ( PageView.objects.all().count() == 0 ) @@ -128,13 +130,13 @@ def test_increase_page_view_count(self): feature, _ = Feature.objects.get_or_create( feature_id=Feature.STORE_PAGEVIEWS, ) - project.feature_set.add(feature) + self.project.feature_set.add(feature) # testing for yesterday with mock.patch('readthedocs.analytics.tasks.timezone.now') as mocked_timezone: - mocked_timezone.return_value = yesterday + mocked_timezone.return_value = self.yesterday - increase_page_view_count(None, context=context, origin=origin) + self.client.get(self.url, HTTP_HOST=self.host) assert ( PageView.objects.all().count() == 1 @@ -143,7 +145,7 @@ def test_increase_page_view_count(self): PageView.objects.all().first().view_count == 1 ), '\'index\' has 1 view' - increase_page_view_count(None, context=context, origin=origin) + self.client.get(self.url, HTTP_HOST=self.host) assert ( PageView.objects.all().count() == 1 @@ -155,8 +157,9 @@ def test_increase_page_view_count(self): # testing for today with mock.patch('readthedocs.analytics.tasks.timezone.now') as mocked_timezone: - mocked_timezone.return_value = today - increase_page_view_count(None, context=context, origin=origin) + mocked_timezone.return_value = self.today + + self.client.get(self.url, HTTP_HOST=self.host) assert ( PageView.objects.all().count() == 2 @@ -168,8 +171,9 @@ def test_increase_page_view_count(self): # testing for tomorrow with mock.patch('readthedocs.analytics.tasks.timezone.now') as mocked_timezone: - mocked_timezone.return_value = tomorrow - increase_page_view_count(None, context=context, origin=origin) + mocked_timezone.return_value = self.tomorrow + + self.client.get(self.url, HTTP_HOST=self.host) assert ( PageView.objects.all().count() == 3 diff --git a/readthedocs/core/unresolver.py b/readthedocs/core/unresolver.py index 9569859fcf2..4db95ea9d44 100644 --- a/readthedocs/core/unresolver.py +++ b/readthedocs/core/unresolver.py @@ -27,11 +27,10 @@ def unresolve(self, url): """ parsed = urlparse(url) domain = parsed.netloc.split(':', 1)[0] - path = parsed.path # TODO: Make this not depend on the request object, # but instead move all this logic here working on strings. - request = RequestFactory().get(path=path, HTTP_HOST=domain) + request = RequestFactory().get(path=parsed.path, HTTP_HOST=domain) project_slug = map_host_to_project_slug(request) # Handle returning a response @@ -39,7 +38,7 @@ def unresolve(self, url): return None request.host_project_slug = request.slug = project_slug - return self.unresolve_from_request(request, path) + return self.unresolve_from_request(request, url) def unresolve_from_request(self, request, path): """ @@ -65,7 +64,7 @@ def unresolve_from_request(self, request, path): mixin = ServeDocsMixin() version_slug = mixin.get_version_from_host(request, kwargs.get('version_slug')) - final_project, lang_slug, version_slug, filename = _get_project_data_from_request( + final_project, lang_slug, version_slug, filename = _get_project_data_from_request( # noqa request, project_slug=project_slug, subproject_slug=kwargs.get('subproject_slug'), From 552becc38e887bc81cff781780782f985a5d247e Mon Sep 17 00:00:00 2001 From: Santos Gallegos Date: Wed, 15 Jul 2020 18:15:53 -0500 Subject: [PATCH 7/8] Typo --- readthedocs/rtd_tests/tests/test_footer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readthedocs/rtd_tests/tests/test_footer.py b/readthedocs/rtd_tests/tests/test_footer.py index a2e41a65210..13cc5fa67c9 100644 --- a/readthedocs/rtd_tests/tests/test_footer.py +++ b/readthedocs/rtd_tests/tests/test_footer.py @@ -452,7 +452,7 @@ def test_version_queries(self): response = self.client.get(self.url, HTTP_HOST=self.host) self.assertContains(response, '0.8.1') - # Seconde time we don't create a new page view, + # 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, HTTP_HOST=self.host) From 7a18594f4bdc7a7c44c38775f51269569746c532 Mon Sep 17 00:00:00 2001 From: Santos Gallegos Date: Wed, 26 Aug 2020 17:48:39 -0500 Subject: [PATCH 8/8] Rename origin -> absolute_uri --- package-lock.json | 2 +- readthedocs/analytics/signals.py | 6 +++--- readthedocs/analytics/tests.py | 18 +++++++++--------- readthedocs/api/v2/signals.py | 2 +- readthedocs/api/v2/views/footer_views.py | 6 +++--- .../static-src/core/js/doc-embed/footer.js | 2 +- .../static/core/js/readthedocs-doc-embed.js | 2 +- readthedocs/core/unresolver.py | 2 +- readthedocs/rtd_tests/tests/test_footer.py | 2 +- 9 files changed, 21 insertions(+), 21 deletions(-) diff --git a/package-lock.json b/package-lock.json index a41f1567c9b..b6c4eed2520 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "readthedocs", - "version": "3.11.4", + "version": "5.4.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/readthedocs/analytics/signals.py b/readthedocs/analytics/signals.py index 2b6fd9f4846..fb95616c609 100644 --- a/readthedocs/analytics/signals.py +++ b/readthedocs/analytics/signals.py @@ -11,7 +11,7 @@ @receiver(footer_response) -def increase_page_view_count(sender, *, request, context, origin, **kwargs): +def increase_page_view_count(sender, *, request, context, absolute_uri, **kwargs): """Increase the page view count for the given project.""" # unused del sender @@ -20,10 +20,10 @@ def increase_page_view_count(sender, *, request, context, origin, **kwargs): project = context['project'] version = context['version'] - if not origin or not project.has_feature(Feature.STORE_PAGEVIEWS): + if not absolute_uri or not project.has_feature(Feature.STORE_PAGEVIEWS): return - unresolved = unresolve_from_request(request, origin) + unresolved = unresolve_from_request(request, absolute_uri) if not unresolved: return diff --git a/readthedocs/analytics/tests.py b/readthedocs/analytics/tests.py index 965b97b1e3d..7b3811561c0 100644 --- a/readthedocs/analytics/tests.py +++ b/readthedocs/analytics/tests.py @@ -104,12 +104,12 @@ def setUp(self): slug='pip', ) self.version = get(Version, slug='1.8', project=self.project) - self.origin = f'https://{self.project.slug}.readthedocs.io/en/latest/index.html' + 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/' + - f'&origin={self.origin}' + f'&absolute_uri={self.absolute_uri}' ) self.today = timezone.now() @@ -140,7 +140,7 @@ def test_increase_page_view_count(self): assert ( PageView.objects.all().count() == 1 - ), f'PageView object for path \'{origin}\' is created' + ), f'PageView object for path \'{self.absolute_uri}\' is created' assert ( PageView.objects.all().first().view_count == 1 ), '\'index\' has 1 view' @@ -149,11 +149,11 @@ def test_increase_page_view_count(self): assert ( PageView.objects.all().count() == 1 - ), f'PageView object for path \'{origin}\' is already created' + ), f'PageView object for path \'{self.absolute_uri}\' is already created' assert PageView.objects.filter(path='index.html').count() == 1 assert ( PageView.objects.all().first().view_count == 2 - ), f'\'{origin}\' has 2 views now' + ), f'\'{self.absolute_uri}\' has 2 views now' # testing for today with mock.patch('readthedocs.analytics.tasks.timezone.now') as mocked_timezone: @@ -163,11 +163,11 @@ def test_increase_page_view_count(self): assert ( PageView.objects.all().count() == 2 - ), f'PageView object for path \'{origin}\' is created for two days (yesterday and today)' + ), f'PageView object for path \'{self.absolute_uri}\' is created for two days (yesterday and today)' assert PageView.objects.filter(path='index.html').count() == 2 assert ( PageView.objects.all().order_by('-date').first().view_count == 1 - ), f'\'{origin}\' has 1 view today' + ), f'\'{self.absolute_uri}\' has 1 view today' # testing for tomorrow with mock.patch('readthedocs.analytics.tasks.timezone.now') as mocked_timezone: @@ -177,8 +177,8 @@ def test_increase_page_view_count(self): assert ( PageView.objects.all().count() == 3 - ), f'PageView object for path \'{origin}\' is created for three days (yesterday, today & tomorrow)' + ), f'PageView object for path \'{self.absolute_uri}\' is created for three days (yesterday, today & tomorrow)' assert PageView.objects.filter(path='index.html').count() == 3 assert ( PageView.objects.all().order_by('-date').first().view_count == 1 - ), f'\'{origin}\' has 1 view tomorrow' + ), f'\'{self.absolute_uri}\' has 1 view tomorrow' diff --git a/readthedocs/api/v2/signals.py b/readthedocs/api/v2/signals.py index f0c149d2c9b..dfb06d77540 100644 --- a/readthedocs/api/v2/signals.py +++ b/readthedocs/api/v2/signals.py @@ -4,5 +4,5 @@ footer_response = django.dispatch.Signal( - providing_args=['request', 'context', 'response_data', 'origin'], + 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 14818040eda..759244a4ee1 100644 --- a/readthedocs/api/v2/views/footer_views.py +++ b/readthedocs/api/v2/views/footer_views.py @@ -89,8 +89,8 @@ class BaseFooterHTML(APIView): - project - version - page: Sphinx's page name, used for path operations, - like change between languages (deprecated in favor of ``origin``). - - origin: Full path with domain, used for path operations. + like change between languages (deprecated in favor of ``absolute_uri``). + - absolute_uri: Full path with domain, used for path operations. - 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. @@ -250,7 +250,7 @@ def get(self, request, format=None): request=request, context=context, response_data=resp_data, - origin=self.request.GET.get('origin'), + 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 cc734cc2183..b7fc76f3ab0 100644 --- a/readthedocs/core/static-src/core/js/doc-embed/footer.js +++ b/readthedocs/core/static-src/core/js/doc-embed/footer.js @@ -44,7 +44,7 @@ function init() { project: rtd['project'], version: rtd['version'], page: rtd['page'], - origin: window.location.href, + absolute_uri: window.location.href, theme: rtd.get_theme_name(), format: "jsonp", }; diff --git a/readthedocs/core/static/core/js/readthedocs-doc-embed.js b/readthedocs/core/static/core/js/readthedocs-doc-embed.js index 5f59584502a..bff2fa451b3 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,w=/u\s*r\s*l\s*\(.*/gi;function b(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(w.lastIndex=0,w.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=b,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:10}],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=b.spaceIndex(e);if(-1===t)return{html:"",closing:"/"===e[e.length-2]};var i="/"===(e=b.trim(e.slice(t+1,-1)))[e.length-1];return i&&(e=b.trim(e.slice(0,-1))),{html:e,closing:i}}(i),l=d[r],c=w(s.html,function(e,t){var i,n=-1!==b.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:10}],7:[function(e,t,i){var n,r;n=this,r=function(){var T=!0;function a(i){function e(e){var t=i.match(e);return t&&1t[1][i])return 1;if(t[0][i]!==t[1][i])return-1;if(0===i)return 0}}function o(e,t,i){var n=s;"string"==typeof t&&(i=t,t=void 0),void 0===t&&(t=!1),i&&(n=a(i));var r=""+n.version;for(var o in e)if(e.hasOwnProperty(o)&&n[o]){if("string"!=typeof e[o])throw new Error("Browser version in the minVersion map should be a string: "+o+": "+String(e));return E([r,e[o]])<0}return t}return s.test=function(e){for(var t=0;t");if(a.append($("

").append($("",{href:r.path,text:r.title}))),r.project!==b){var s="(from project "+r.project+")";a.append($("",{text:s}))}for(var l=0;lC&&(h=h.substr(0,C)+" ...");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."),w()}).fail(function(e){console.log("Read the Docs search failed. Falling back to MkDocs search."),w()}),$.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 b=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=O(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,C)+" ..."];if(f.highlights.title.length&&(g=O(f.highlights.title[0])),f.highlights.content.length){var w=f.highlights.content;v=[];for(var b=0;b<%= 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,C)+" ..."),y.highlights.content.length&&(E="... "+O(y.highlights.content[0])+" ..."),y.highlights.name.length&&(T=O(y.highlights.name[0])),M(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":16}],18:[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 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":16}],19:[function(e,t,i){var l,c=e("./constants"),d=e("./rtd-data"),n=e("bowser"),u="#ethical-ad-placement";function h(){var e,t,i="rtd-"+(Math.random()+1).toString(36).substring(4),n=c.PROMO_TYPES.LEFTNAV,r=c.DEFAULT_PROMO_PRIORITY,o=null;return l.is_mkdocs_builder()&&l.is_rtd_like_theme()?(o="nav.wy-nav-side",e="ethical-rtd ethical-dark-theme"):l.is_rtd_like_theme()?(o="nav.wy-nav-side > div.wy-side-scroll",e="ethical-rtd ethical-dark-theme"):l.is_alabaster_like_theme()&&(o="div.sphinxsidebar > div.sphinxsidebarwrapper",e="ethical-alabaster"),o?($("
").attr("id",i).addClass(e).appendTo(o),(!(t=$("#"+i).offset())||t.top>$(window).height())&&(r=c.LOW_PROMO_PRIORITY),{div_id:i,display_type:n,priority:r}):null}function p(){var e,t,i="rtd-"+(Math.random()+1).toString(36).substring(4),n=c.PROMO_TYPES.FOOTER,r=c.DEFAULT_PROMO_PRIORITY,o=null;return l.is_rtd_like_theme()?(o=$("
").insertAfter("footer hr"),e="ethical-rtd"):l.is_alabaster_like_theme()&&(o="div.bodywrapper .body",e="ethical-alabaster"),o?($("
").attr("id",i).addClass(e).appendTo(o),(!(t=$("#"+i).offset())||t.top<$(window).height())&&(r=c.LOW_PROMO_PRIORITY),{div_id:i,display_type:n,priority:r}):null}function f(){var e="rtd-"+(Math.random()+1).toString(36).substring(4),t=c.PROMO_TYPES.FIXED_FOOTER,i=c.DEFAULT_PROMO_PRIORITY;return n&&n.mobile&&(i=c.MAXIMUM_PROMO_PRIORITY),$("
").attr("id",e).appendTo("body"),{div_id:e,display_type:t,priority:i}}function g(e){this.id=e.id,this.div_id=e.div_id||"",this.html=e.html||"",this.display_type=e.display_type||"",this.view_tracking_url=e.view_url,this.click_handler=function(){"undefined"!=typeof ga?ga("rtfd.send","event","Promo","Click",e.id):"undefined"!=typeof _gaq&&_gaq.push(["rtfd._setAccount","UA-17997319-1"],["rtfd._trackEvent","Promo","Click",e.id])}}g.prototype.display=function(){var e="#"+this.div_id,t=this.view_tracking_url;$(e).html(this.html),$(e).find('a[href*="/sustainability/click/"]').on("click",this.click_handler);function i(){$.inViewport($(e),-3)&&($("").attr("src",t).css("display","none").appendTo(e),$(window).off(".rtdinview"),$(".wy-side-scroll").off(".rtdinview"))}$(window).on("DOMContentLoaded.rtdinview load.rtdinview scroll.rtdinview resize.rtdinview",i),$(".wy-side-scroll").on("scroll.rtdinview",i),$(".ethical-close").on("click",function(){return $(e).hide(),!1}),this.post_promo_display()},g.prototype.disable=function(){$("#"+this.div_id).hide()},g.prototype.post_promo_display=function(){this.display_type===c.PROMO_TYPES.FOOTER&&($("
").insertAfter("#"+this.div_id),$("
").insertBefore("#"+this.div_id+".ethical-alabaster .ethical-footer"))},t.exports={Promo:g,init:function(){var e,t,i={format:"jsonp"},n=[],r=[],o=[],a=[p,h,f];if(l=d.get(),t=function(){var e,t="rtd-"+(Math.random()+1).toString(36).substring(4),i=c.PROMO_TYPES.LEFTNAV;return e=l.is_rtd_like_theme()?"ethical-rtd ethical-dark-theme":"ethical-alabaster",0<$(u).length?($("
").attr("id",t).addClass(e).appendTo(u),{div_id:t,display_type:i}):null}())n.push(t.div_id),r.push(t.display_type),o.push(t.priority||c.DEFAULT_PROMO_PRIORITY),!0;else{if(!l.show_promo())return;for(var s=0;s").attr("id","rtd-detection").attr("class","ethical-rtd").html(" ").appendTo("body"),0===$("#rtd-detection").height()&&(e=!0),$("#rtd-detection").remove(),e}()&&(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(){var e=h(),t=null;e&&e.div_id&&(t=$("#"+e.div_id).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))}())}})}}},{"./constants":14,"./rtd-data":16,bowser:7}],20:[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":16}],21:[function(e,t,i){var n=e("./doc-embed/sponsorship"),r=e("./doc-embed/footer.js"),o=(e("./doc-embed/rtd-data"),e("./doc-embed/sphinx")),a=e("./doc-embed/search");$.extend(e("verge")),$(document).ready(function(){r.init(),o.init(),a.init(),n.init()})},{"./doc-embed/footer.js":15,"./doc-embed/rtd-data":16,"./doc-embed/search":17,"./doc-embed/sphinx":18,"./doc-embed/sponsorship":19,verge:13}]},{},[21]); +!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,w=/u\s*r\s*l\s*\(.*/gi;function b(e){return e.replace(u,""")}function _(e){return e.replace(h,'"')}function y(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(w.lastIndex=0,w.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=b,i.unescapeQuote=_,i.escapeHtmlEntities=y,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:10}],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=b.spaceIndex(e);if(-1===t)return{html:"",closing:"/"===e[e.length-2]};var i="/"===(e=b.trim(e.slice(t+1,-1)))[e.length-1];return i&&(e=b.trim(e.slice(0,-1))),{html:e,closing:i}}(i),l=d[r],c=w(s.html,function(e,t){var i,n=-1!==b.indexOf(l,e);return _(i=p(r,e,t,n))?n?(t=g(r,e,t,v))?e+'="'+t+'"':e:_(i=f(r,e,t,n))?void 0:i:i});i="<"+r;return c&&(i+=" "+c),s.closing&&(i+=" /"),i+=">"}return _(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:10}],7:[function(e,t,i){var n,r;n=this,r=function(){var T=!0;function a(i){function e(e){var t=i.match(e);return t&&1t[1][i])return 1;if(t[0][i]!==t[1][i])return-1;if(0===i)return 0}}function o(e,t,i){var n=s;"string"==typeof t&&(i=t,t=void 0),void 0===t&&(t=!1),i&&(n=a(i));var r=""+n.version;for(var o in e)if(e.hasOwnProperty(o)&&n[o]){if("string"!=typeof e[o])throw new Error("Browser version in the minVersion map should be a string: "+o+": "+String(e));return E([r,e[o]])<0}return t}return s.test=function(e){for(var t=0;t");if(a.append($("

").append($("",{href:r.path,text:r.title}))),r.project!==b){var s="(from project "+r.project+")";a.append($("",{text:s}))}for(var l=0;lC&&(h=h.substr(0,C)+" ...");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."),w()}).fail(function(e){console.log("Read the Docs search failed. Falling back to MkDocs search."),w()}),$.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 b=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=O(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,C)+" ..."];if(f.highlights.title.length&&(g=O(f.highlights.title[0])),f.highlights.content.length){var w=f.highlights.content;v=[];for(var b=0;b<%= 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,C)+" ..."),y.highlights.content.length&&(E="... "+O(y.highlights.content[0])+" ..."),y.highlights.name.length&&(T=O(y.highlights.name[0])),M(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":16}],18:[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 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":16}],19:[function(e,t,i){var l,c=e("./constants"),d=e("./rtd-data"),n=e("bowser"),u="#ethical-ad-placement";function h(){var e,t,i="rtd-"+(Math.random()+1).toString(36).substring(4),n=c.PROMO_TYPES.LEFTNAV,r=c.DEFAULT_PROMO_PRIORITY,o=null;return l.is_mkdocs_builder()&&l.is_rtd_like_theme()?(o="nav.wy-nav-side",e="ethical-rtd ethical-dark-theme"):l.is_rtd_like_theme()?(o="nav.wy-nav-side > div.wy-side-scroll",e="ethical-rtd ethical-dark-theme"):l.is_alabaster_like_theme()&&(o="div.sphinxsidebar > div.sphinxsidebarwrapper",e="ethical-alabaster"),o?($("
").attr("id",i).addClass(e).appendTo(o),(!(t=$("#"+i).offset())||t.top>$(window).height())&&(r=c.LOW_PROMO_PRIORITY),{div_id:i,display_type:n,priority:r}):null}function p(){var e,t,i="rtd-"+(Math.random()+1).toString(36).substring(4),n=c.PROMO_TYPES.FOOTER,r=c.DEFAULT_PROMO_PRIORITY,o=null;return l.is_rtd_like_theme()?(o=$("
").insertAfter("footer hr"),e="ethical-rtd"):l.is_alabaster_like_theme()&&(o="div.bodywrapper .body",e="ethical-alabaster"),o?($("
").attr("id",i).addClass(e).appendTo(o),(!(t=$("#"+i).offset())||t.top<$(window).height())&&(r=c.LOW_PROMO_PRIORITY),{div_id:i,display_type:n,priority:r}):null}function f(){var e="rtd-"+(Math.random()+1).toString(36).substring(4),t=c.PROMO_TYPES.FIXED_FOOTER,i=c.DEFAULT_PROMO_PRIORITY;return n&&n.mobile&&(i=c.MAXIMUM_PROMO_PRIORITY),$("
").attr("id",e).appendTo("body"),{div_id:e,display_type:t,priority:i}}function g(e){this.id=e.id,this.div_id=e.div_id||"",this.html=e.html||"",this.display_type=e.display_type||"",this.view_tracking_url=e.view_url,this.click_handler=function(){"undefined"!=typeof ga?ga("rtfd.send","event","Promo","Click",e.id):"undefined"!=typeof _gaq&&_gaq.push(["rtfd._setAccount","UA-17997319-1"],["rtfd._trackEvent","Promo","Click",e.id])}}g.prototype.display=function(){var e="#"+this.div_id,t=this.view_tracking_url;$(e).html(this.html),$(e).find('a[href*="/sustainability/click/"]').on("click",this.click_handler);function i(){$.inViewport($(e),-3)&&($("").attr("src",t).css("display","none").appendTo(e),$(window).off(".rtdinview"),$(".wy-side-scroll").off(".rtdinview"))}$(window).on("DOMContentLoaded.rtdinview load.rtdinview scroll.rtdinview resize.rtdinview",i),$(".wy-side-scroll").on("scroll.rtdinview",i),$(".ethical-close").on("click",function(){return $(e).hide(),!1}),this.post_promo_display()},g.prototype.disable=function(){$("#"+this.div_id).hide()},g.prototype.post_promo_display=function(){this.display_type===c.PROMO_TYPES.FOOTER&&($("
").insertAfter("#"+this.div_id),$("
").insertBefore("#"+this.div_id+".ethical-alabaster .ethical-footer"))},t.exports={Promo:g,init:function(){var e,t,i={format:"jsonp"},n=[],r=[],o=[],a=[p,h,f];if(l=d.get(),t=function(){var e,t="rtd-"+(Math.random()+1).toString(36).substring(4),i=c.PROMO_TYPES.LEFTNAV;return e=l.is_rtd_like_theme()?"ethical-rtd ethical-dark-theme":"ethical-alabaster",0<$(u).length?($("
").attr("id",t).addClass(e).appendTo(u),{div_id:t,display_type:i}):null}())n.push(t.div_id),r.push(t.display_type),o.push(t.priority||c.DEFAULT_PROMO_PRIORITY),!0;else{if(!l.show_promo())return;for(var s=0;s").attr("id","rtd-detection").attr("class","ethical-rtd").html(" ").appendTo("body"),0===$("#rtd-detection").height()&&(e=!0),$("#rtd-detection").remove(),e}()&&(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(){var e=h(),t=null;e&&e.div_id&&(t=$("#"+e.div_id).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))}())}})}}},{"./constants":14,"./rtd-data":16,bowser:7}],20:[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":16}],21:[function(e,t,i){var n=e("./doc-embed/sponsorship"),r=e("./doc-embed/footer.js"),o=(e("./doc-embed/rtd-data"),e("./doc-embed/sphinx")),a=e("./doc-embed/search");$.extend(e("verge")),$(document).ready(function(){r.init(),o.init(),a.init(),n.init()})},{"./doc-embed/footer.js":15,"./doc-embed/rtd-data":16,"./doc-embed/search":17,"./doc-embed/sphinx":18,"./doc-embed/sponsorship":19,verge:13}]},{},[21]); \ No newline at end of file diff --git a/readthedocs/core/unresolver.py b/readthedocs/core/unresolver.py index 4db95ea9d44..38677f94828 100644 --- a/readthedocs/core/unresolver.py +++ b/readthedocs/core/unresolver.py @@ -51,7 +51,7 @@ def unresolve_from_request(self, request, path): """ parsed = urlparse(path) path = parsed.path - project_slug = getattr(request, 'slug', None) + project_slug = getattr(request, 'host_project_slug', None) if not project_slug: return None diff --git a/readthedocs/rtd_tests/tests/test_footer.py b/readthedocs/rtd_tests/tests/test_footer.py index 1be1db40777..0d6bdb78e2d 100644 --- a/readthedocs/rtd_tests/tests/test_footer.py +++ b/readthedocs/rtd_tests/tests/test_footer.py @@ -449,7 +449,7 @@ def setUp(self): self.url = ( reverse('footer_html') + f'?project={self.pip.slug}&version={self.latest.slug}&page=index&docroot=/docs/' + - '&origin=https://pip.readthedocs.io/en/latest/index.html' + '&absolute_uri=https://pip.readthedocs.io/en/latest/index.html' ) self.host = 'pip.readthedocs.io'