From febfd17edbcfdcb205b55d65dc8041121722dec0 Mon Sep 17 00:00:00 2001 From: Anthony Johnson Date: Fri, 5 Feb 2016 13:57:31 -0500 Subject: [PATCH 1/5] Try subproject query on search --- readthedocs/restapi/views/search_views.py | 5 ++- readthedocs/search/lib.py | 45 +++++++++++++++++------ readthedocs/search/views.py | 4 +- 3 files changed, 39 insertions(+), 15 deletions(-) diff --git a/readthedocs/restapi/views/search_views.py b/readthedocs/restapi/views/search_views.py index 584cb55c3a9..d1781c69d06 100644 --- a/readthedocs/restapi/views/search_views.py +++ b/readthedocs/restapi/views/search_views.py @@ -6,6 +6,7 @@ from readthedocs.builds.constants import LATEST from readthedocs.builds.models import Version +from readthedocs.projects.models import Project from readthedocs.search.lib import search_file, search_project, search_section from readthedocs.restapi import utils @@ -44,8 +45,8 @@ def search(request): return Response({'error': 'Need project and q'}, status=status.HTTP_400_BAD_REQUEST) log.debug("(API Search) %s", query) - results = search_file(request=request, project=project_slug, - version=version_slug, query=query) + results = search_file(request=request, project_slug=project_slug, + version_slug=version_slug, query=query) return Response({'results': results}) diff --git a/readthedocs/search/lib.py b/readthedocs/search/lib.py index 75cf134621e..92330c7933c 100644 --- a/readthedocs/search/lib.py +++ b/readthedocs/search/lib.py @@ -1,7 +1,7 @@ -from readthedocs.builds.constants import LATEST - from .indexes import PageIndex, ProjectIndex, SectionIndex +from readthedocs.builds.constants import LATEST +from readthedocs.projects.models import Project from readthedocs.search.signals import (before_project_search, before_file_search, before_section_search) @@ -42,7 +42,24 @@ def search_project(request, query, language=None): return ProjectIndex().search(body) -def search_file(request, query, project=None, version=LATEST, taxonomy=None): +def search_file(request, query, project_slug=None, version_slug=LATEST, taxonomy=None): + """Search index for files matching query + + Raises a 404 error on missing project + + :param request: request instance + :param query: string to query for + :param project_slug: :py:cls:`Project` slug + :param version_slug: slug for :py:cls:`Project` version slug + :param taxonomy: taxonomy for search + """ + try: + project = (Project.objects + .api(request.user) + .get(slug=project_slug)) + except Project.DoesNotExist: + raise Http404("Project does not exist") + kwargs = {} body = { "query": { @@ -93,17 +110,24 @@ def search_file(request, query, project=None, version=LATEST, taxonomy=None): "size": 50 # TODO: Support pagination. } - if project or version or taxonomy: + if project_slug or version_slug or taxonomy: final_filter = {"and": []} if project: - final_filter['and'].append({'term': {'project': project}}) - + project_slugs = [project.slug] + project_slugs.extend(s.child.slug for s in project.subprojects.all()) + project_slugs.extend(s.parent.slug for s in project.superprojects.all()) + body['filter'] = { + "and": [ + {"terms": {"project": project_slugs}}, + {"term": {"version": version_slug}}, + ] + } # Add routing to optimize search by hitting the right shard. - kwargs['routing'] = project + kwargs['routing'] = project_slug - if version: - final_filter['and'].append({'term': {'version': version}}) + if version_slug: + final_filter['and'].append({'term': {'version': version_slug}}) if taxonomy: final_filter['and'].append({'term': {'taxonomy': taxonomy}}) @@ -115,8 +139,7 @@ def search_file(request, query, project=None, version=LATEST, taxonomy=None): before_file_search.send(request=request, sender=PageIndex, body=body) - results = PageIndex().search(body, **kwargs) - return results + return PageIndex().search(body, **kwargs) def search_section(request, query, project_slug=None, version_slug=LATEST, diff --git a/readthedocs/search/views.py b/readthedocs/search/views.py index a610fc6d4bb..fe129ba0616 100644 --- a/readthedocs/search/views.py +++ b/readthedocs/search/views.py @@ -51,8 +51,8 @@ def elastic_search(request): if type == 'project': results = search_lib.search_project(request, query, language=language) elif type == 'file': - results = search_lib.search_file(request, query, project=project, - version=version, + results = search_lib.search_file(request, query, project_slug=project, + version_slug=version, taxonomy=taxonomy) if results: From e44ca593f4933560cc4fc588c59f542628795234 Mon Sep 17 00:00:00 2001 From: Anthony Johnson Date: Sun, 14 Feb 2016 15:40:42 -0800 Subject: [PATCH 2/5] Fix subproject query --- readthedocs/search/lib.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/readthedocs/search/lib.py b/readthedocs/search/lib.py index 92330c7933c..167ae95f455 100644 --- a/readthedocs/search/lib.py +++ b/readthedocs/search/lib.py @@ -1,3 +1,5 @@ +from django.http import Http404 + from .indexes import PageIndex, ProjectIndex, SectionIndex from readthedocs.builds.constants import LATEST @@ -117,12 +119,8 @@ def search_file(request, query, project_slug=None, version_slug=LATEST, taxonomy project_slugs = [project.slug] project_slugs.extend(s.child.slug for s in project.subprojects.all()) project_slugs.extend(s.parent.slug for s in project.superprojects.all()) - body['filter'] = { - "and": [ - {"terms": {"project": project_slugs}}, - {"term": {"version": version_slug}}, - ] - } + final_filter['and'].append({"terms": {"project": project_slugs}}) + # Add routing to optimize search by hitting the right shard. kwargs['routing'] = project_slug From 9ad435bb02c98983c05cbb946c7fd08d73163df4 Mon Sep 17 00:00:00 2001 From: Anthony Johnson Date: Mon, 22 Feb 2016 14:34:01 -0800 Subject: [PATCH 3/5] Improve on routing updates and querying --- readthedocs/restapi/utils.py | 41 +++++++++++++++++++++++------------- readthedocs/search/lib.py | 36 ++++++++++++++++++------------- 2 files changed, 47 insertions(+), 30 deletions(-) diff --git a/readthedocs/restapi/utils.py b/readthedocs/restapi/utils.py index 2cbfe50cece..7aec040bf0e 100644 --- a/readthedocs/restapi/utils.py +++ b/readthedocs/restapi/utils.py @@ -85,14 +85,16 @@ def delete_versions(project, version_data): def index_search_request(version, page_list, commit, project_scale, page_scale, section=True, delete=True): - log_msg = ' '.join([page['path'] for page in page_list]) - log.info("(Server Search) Indexing Pages: %s [%s]" % ( - version.project.slug, log_msg)) + """Update search indexes with build output JSON + + In order to keep sub-projects all indexed on the same shard, indexes will be + updated using the parent project's slug as the routing value. + """ project = version.project - page_obj = PageIndex() - section_obj = SectionIndex() - # tags = [tag.name for tag in project.tags.all()] + log_msg = ' '.join([page['path'] for page in page_list]) + log.info("Updating search index: project=%s pages=[%s]", + project.slug, log_msg) project_obj = ProjectIndex() project_obj.index_document(data={ @@ -107,11 +109,17 @@ def index_search_request(version, page_list, commit, project_scale, page_scale, 'weight': project_scale, }) + page_obj = PageIndex() + section_obj = SectionIndex() index_list = [] section_index_list = [] + routes = [project.slug] + routes.extend([p.parent.slug for p in project.superprojects.all()]) for page in page_list: - log.debug("(API Index) %s:%s" % (project.slug, page['path'])) - page_id = hashlib.md5('%s-%s-%s' % (project.slug, version.slug, page['path'])).hexdigest() + log.debug("Indexing page: %s:%s" % (project.slug, page['path'])) + page_id = (hashlib + .md5('-'.join([project.slug, version.slug, page['path']])) + .hexdigest()) index_list.append({ 'id': page_id, 'project': project.slug, @@ -127,10 +135,10 @@ def index_search_request(version, page_list, commit, project_scale, page_scale, if section: for section in page['sections']: section_index_list.append({ - 'id': hashlib.md5( - '%s-%s-%s-%s' % (project.slug, version.slug, - page['path'], section['id']) - ).hexdigest(), + 'id': (hashlib + .md5('-'.join([project.slug, version.slug, + page['path'], section['id']])) + .hexdigest()), 'project': project.slug, 'version': version.slug, 'path': page['path'], @@ -139,12 +147,15 @@ def index_search_request(version, page_list, commit, project_scale, page_scale, 'content': section['content'], 'weight': page_scale, }) - section_obj.bulk_index(section_index_list, parent=page_id, routing=project.slug) + for route in routes: + section_obj.bulk_index(section_index_list, parent=page_id, + routing=route) - page_obj.bulk_index(index_list, parent=project.slug) + for route in routes: + page_obj.bulk_index(index_list, parent=project.slug, routing=route) if delete: - log.info("(Server Search) Deleting files not in commit: %s" % commit) + log.info("Deleting files not in commit: %s", commit) # TODO: AK Make sure this works delete_query = { "query": { diff --git a/readthedocs/search/lib.py b/readthedocs/search/lib.py index 167ae95f455..a7625e0dd91 100644 --- a/readthedocs/search/lib.py +++ b/readthedocs/search/lib.py @@ -55,13 +55,6 @@ def search_file(request, query, project_slug=None, version_slug=LATEST, taxonomy :param version_slug: slug for :py:cls:`Project` version slug :param taxonomy: taxonomy for search """ - try: - project = (Project.objects - .api(request.user) - .get(slug=project_slug)) - except Project.DoesNotExist: - raise Http404("Project does not exist") - kwargs = {} body = { "query": { @@ -115,14 +108,27 @@ def search_file(request, query, project_slug=None, version_slug=LATEST, taxonomy if project_slug or version_slug or taxonomy: final_filter = {"and": []} - if project: - project_slugs = [project.slug] - project_slugs.extend(s.child.slug for s in project.subprojects.all()) - project_slugs.extend(s.parent.slug for s in project.superprojects.all()) - final_filter['and'].append({"terms": {"project": project_slugs}}) - - # Add routing to optimize search by hitting the right shard. - kwargs['routing'] = project_slug + if project_slug: + try: + project = (Project.objects + .api(request.user) + .get(slug=project_slug)) + project_slugs = [project.slug] + project_slugs.extend(s.child.slug for s + in project.subprojects.all()) + final_filter['and'].append({"terms": {"project": project_slugs}}) + + # Add routing to optimize search by hitting the right shard. + # This purposely doesn't apply routing if the project has more + # than one parent project. + if project.superprojects.exists(): + if project.superprojects.count() == 1: + kwargs['routing'] = (project.superprojects.first() + .parent.slug) + else: + kwargs['routing'] = project_slug + except Project.DoesNotExist: + return None if version_slug: final_filter['and'].append({'term': {'version': version_slug}}) From 6fbe7d7437f9d8c28276e409bf1ecad1433a9c7c Mon Sep 17 00:00:00 2001 From: Anthony Johnson Date: Tue, 23 Feb 2016 11:43:35 -0800 Subject: [PATCH 4/5] Add subproject canonical URLs to search result return --- .../core/static-src/core/js/doc-embed/search.js | 5 +++++ .../core/static/core/js/readthedocs-doc-embed.js | 2 +- .../templates/doc_builder/conf.py.tmpl | 3 +++ readthedocs/projects/models.py | 16 ++++++++++++++++ readthedocs/templates/sphinx/layout.html | 4 ++++ 5 files changed, 29 insertions(+), 1 deletion(-) diff --git a/readthedocs/core/static-src/core/js/doc-embed/search.js b/readthedocs/core/static-src/core/js/doc-embed/search.js index 8e64c88d85a..18a5136dca6 100644 --- a/readthedocs/core/static-src/core/js/doc-embed/search.js +++ b/readthedocs/core/static-src/core/js/doc-embed/search.js @@ -21,6 +21,7 @@ function attach_elastic_search_query(data) { version = data.version, language = data.language || 'en', api_host = data.api_host, + subprojects = data.subprojects || {}, canonical_url = data.canonical_url || "/"; var query_override = function (query) { @@ -46,6 +47,10 @@ function attach_elastic_search_query(data) { highlight = hit.highlight; item_url.href = canonical_url; + if (fields.project != project) { + var subproject_url = subprojects[fields.project]; + item_url.href = subproject_url; + } item_url += fields.path + DOCUMENTATION_OPTIONS.FILE_SUFFIX; item_url.search = '?highlight=' + $.urlencode(query); diff --git a/readthedocs/core/static/core/js/readthedocs-doc-embed.js b/readthedocs/core/static/core/js/readthedocs-doc-embed.js index 6ae6a456b3f..24c1169349b 100644 --- a/readthedocs/core/static/core/js/readthedocs-doc-embed.js +++ b/readthedocs/core/static/core/js/readthedocs-doc-embed.js @@ -1 +1 @@ -!function e(t,n,r){function o(a,s){if(!n[a]){if(!t[a]){var c="function"==typeof require&&require;if(!s&&c)return c(a,!0);if(i)return i(a,!0);var d=new Error("Cannot find module '"+a+"'");throw d.code="MODULE_NOT_FOUND",d}var l=n[a]={exports:{}};t[a][0].call(l.exports,function(e){var n=t[a][1][e];return o(n?n:e)},l,l.exports,e,t,n,r)}return n[a].exports}for(var i="function"==typeof require&&require,a=0;a"),e(".wy-menu-vertical ul").not(".simple").siblings("a").each(function(){var n=e(this);expand=e(''),expand.on("click",function(e){return t.toggleCurrent(n),e.stopPropagation(),!1}),n.prepend(expand)})},e.reset=function(){var e=encodeURI(window.location.hash);if(e)try{var t=$(".wy-menu-vertical").find('[href="'+e+'"]');$(".wy-menu-vertical li.toctree-l1 li.current").removeClass("current"),t.closest("li.toctree-l2").addClass("current"),t.closest("li.toctree-l3").addClass("current"),t.closest("li.toctree-l4").addClass("current")}catch(n){console.log("Error expanding nav for anchor",n)}},e.onScroll=function(){this.winScroll=!1;var e=this.win.scrollTop(),t=e+this.winHeight,n=this.navBar.scrollTop(),r=n+(e-this.winPosition);0>e||t>this.docHeight||(this.navBar.scrollTop(r),this.winPosition=e)},e.onResize=function(){this.winResize=!1,this.winHeight=this.win.height(),this.docHeight=$(document).height()},e.hashChange=function(){this.linkScroll=!0,this.win.one("hashchange",function(){this.linkScroll=!1})},e.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")},e}var o="undefined"!=typeof window?window.jQuery:e("jquery");t.exports.ThemeNav=r(),"undefined"!=typeof window&&(window.SphinxRtdTheme={StickyNav:t.exports.ThemeNav})},{jquery:"jquery"}],2:[function(e,t,n){function r(){var e=a.get(),t={project:e.project,version:e.version,page:e.page,theme:e.get_theme_name(),format:"jsonp"};"docroot"in e&&(t.docroot=e.docroot),"source_suffix"in e&&(t.source_suffix=e.source_suffix),0===window.location.pathname.indexOf("/projects/")&&(t.subproject=!0),$.ajax({url:e.api_host+"/api/v2/footer_html/",crossDomain:!0,xhrFields:{withCredentials:!0},dataType:"jsonp",data:t,success:function(e){s.init(e.version_compare),o(e),i()},error:function(){console.error("Error loading Read the Docs footer")}})}function o(e){var t=a.get();if(t.is_rtd_theme()?$("div.rst-other-versions").html(e.html):$("body").append(e.html),e.version_active?!e.version_supported:$(".rst-current-version").addClass("rst-out-of-date"),e.promo&&t.show_promo()){var n=new c.Promo(e.promo_data.id,e.promo_data.text,e.promo_data.link,e.promo_data.image);n&&n.display()}}function i(){function e(e){return/^(GET|HEAD|OPTIONS|TRACE)$/.test(e)}$.ajaxSetup({beforeSend:function(t,n){e(n.type)||t.setRequestHeader("X-CSRFToken",$("a.bookmark[token]").attr("token"))}})}var a=e("./rtd-data"),s=e("./version-compare"),c=e("../sponsorship");t.exports={init:r}},{"../sponsorship":9,"./rtd-data":4,"./version-compare":7}],3:[function(e,t,n){function r(){var e=o.get();if("builder"in e&&"mkdocs"==e.builder){$("").attr({type:"hidden",name:"project",value:e.project}).appendTo("#rtd-search-form"),$("").attr({type:"hidden",name:"version",value:e.version}).appendTo("#rtd-search-form"),$("").attr({type:"hidden",name:"type",value:"file"}).appendTo("#rtd-search-form"),$("#rtd-search-form").prop("action",e.api_host+"/search/");var t=$("nav.wy-nav-side:first"),n=$(window),r="stickynav",i=function(){t.height()<=n.height()?t.addClass(r):t.removeClass(r)};n.on("resize",i),i()}}var o=e("./rtd-data");t.exports={init:r}},{"./rtd-data":4}],4:[function(e,t,n){function r(){var e=Object.create(o),t={api_host:"https://readthedocs.org"};return $.extend(e,t,window.READTHEDOCS_DATA),e}var o={is_rtd_theme:function(){return"sphinx_rtd_theme"===this.get_theme_name()},is_sphinx_builder:function(){return!("builder"in this)||"mkdocs"!=this.builder},get_theme_name:function(){return"sphinx_rtd_theme"!==this.theme&&1===$("div.rst-other-versions").length?"sphinx_rtd_theme":this.theme},show_promo:function(){return"https://readthedocs.com"!==this.api_host&&this.is_sphinx_builder()&&this.is_rtd_theme()}};t.exports={get:r}},{}],5:[function(e,t,n){function r(){var e=i.get();o(e)}function o(e){var t=e.project,n=e.version,r=e.language||"en",o=e.api_host,i=e.canonical_url||"/",a=function(e){var a=$.Deferred(),s=document.createElement("a");s.href=o,s.pathname="/api/v2/search",s.search="?q="+e+"&project="+t+"&version="+n+"&language="+r,a.then(function(n){var r=n.hits||{},o=r.hits||[];if(o.length)for(var a in o){var s=o[a],c=s.fields||{},d=$('
  • '),l=document.createElement("a"),h=s.highlight;if(l.href=i,l+=c.path+DOCUMENTATION_OPTIONS.FILE_SUFFIX,l.search="?highlight="+$.urlencode(e),d.append($("").attr("href",l).html(c.title)),c.project!=t&&d.append($("").text(" (from project "+c.project+")")),h.content.length){var u=$('
    ').html(h.content[0]);u.find("em").addClass("highlighted"),d.append(u)}Search.output.append(d),d.slideDown(5)}Search.status.text(o.length?_("Search finished, found %s page(s) matching the search query.").replace("%s",o.length):_("Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories."))}).fail(function(t){Search.query_fallback(e)}).always(function(){$("#search-progress").empty(),Search.stopPulse(),Search.title.text(_("Search Results")),Search.status.fadeIn(500)}),$.ajax({url:s.href,complete:function(e,t){return"undefined"==typeof e.responseJSON||"undefined"==typeof e.responseJSON.results?a.reject():a.resolve(e.responseJSON.results)}}).error(function(e,t,n){return a.reject()})};"undefined"!=typeof Search&&(Search.query_fallback=Search.query,Search.query=a),$(document).ready(function(){"undefined"!=typeof Search&&Search.init()})}var i=e("./rtd-data");t.exports={init:r}},{"./rtd-data":4}],6:[function(e,t,n){function r(){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";_gaq&&_gaq.push(["rtfd._setAccount","UA-17997319-1"],["rtfd._trackEvent","Flyout","Click",e])}),!("builder"in e)||"builder"in e&&"mkdocs"!=e.builder){var t=i.ThemeNav;if($(document).ready(function(){setTimeout(function(){t.navBar||t.enable()},1e3)}),e.is_rtd_theme()){var n=jquery("div.wy-side-scroll:first");if(!n.length){var r=jquery("nav.wy-nav-side:first"),a=$("
    ").addClass("wy-side-scroll");r.children().detach().appendTo(a),a.prependTo(r),t.navBar=a}}}}var o=e("./rtd-data"),i=e("./../../../../../../bower_components/sphinx-rtd-theme/js/theme.js");t.exports={init:r}},{"./../../../../../../bower_components/sphinx-rtd-theme/js/theme.js":1,"./rtd-data":4}],7:[function(e,t,n){function r(e){var t=o.get();if(!e.is_highest){var n=window.location.pathname.replace(t.version,e.slug),r=$('');r.find("a").attr("href",n).text(e.version);var i=$("div.body");i.length||(i=$("div.document")),i.prepend(r)}}var o=e("./rtd-data");t.exports={init:r}},{"./rtd-data":4}],8:[function(e,t,n){var r=(e("./sponsorship"),e("./doc-embed/footer.js")),o=e("./doc-embed/mkdocs"),i=(e("./doc-embed/rtd-data"),e("./doc-embed/sphinx")),a=e("./doc-embed/search");$(document).ready(function(){r.init(),i.init(),o.init(),a.init()})},{"./doc-embed/footer.js":2,"./doc-embed/mkdocs":3,"./doc-embed/rtd-data":4,"./doc-embed/search":5,"./doc-embed/sphinx":6,"./sponsorship":9}],9:[function(e,t,n){function r(e,t,n,r){this.id=e,this.text=t,this.link=n,this.image=r,this.promo=null}t.exports={Promo:r},r.prototype.create=function(){function e(){_gaq&&_gaq.push(["rtfd._setAccount","UA-17997319-1"],["rtfd._trackEvent","Promo","Click",t.id])}var t=this,n=$("nav.wy-nav-side");if(n.length){promo=$("
    ").attr("class","wy-menu rst-pro");{var r=$("
    ").attr("class","rst-pro-about"),o=$("").attr("href","http://docs.readthedocs.org/en/latest/sponsors.html#sponsorship-information").appendTo(r);$("").attr("class","fa fa-info-circle").appendTo(o)}if(r.appendTo(promo),t.image){{var i=$("").attr("class","rst-pro-image-wrapper").attr("href",t.link).attr("target","_blank").on("click",e);$("").attr("class","rst-pro-image").attr("src",t.image).appendTo(i)}promo.append(i)}var a=$("").html(t.text);return $(a).find("a").each(function(){$(this).attr("class","rst-pro-link").attr("href",t.link).attr("target","_blank").on("click",e)}),promo.append(a),promo.appendTo(n),promo.wrapper=$("
    ").attr("class","rst-pro-wrapper").appendTo(n),promo}},r.prototype.display=function(){var e=this.promo;e||(e=this.promo=this.create()),e&&e.show()},r.prototype.disable=function(){},r.from_variants=function(e){if(0==e.length)return null;var t=Math.floor(Math.random()*e.length),n=e[t],o=n.text,i=n.link,a=n.image,s=n.id;return new r(s,o,i,a)}},{}]},{},[8]); \ No newline at end of file +!function e(t,r,n){function o(a,s){if(!r[a]){if(!t[a]){var c="function"==typeof require&&require;if(!s&&c)return c(a,!0);if(i)return i(a,!0);var d=new Error("Cannot find module '"+a+"'");throw d.code="MODULE_NOT_FOUND",d}var l=r[a]={exports:{}};t[a][0].call(l.exports,function(e){var r=t[a][1][e];return o(r?r:e)},l,l.exports,e,t,r,n)}return r[a].exports}for(var i="function"==typeof require&&require,a=0;a
    "),e(".wy-menu-vertical ul").not(".simple").siblings("a").each(function(){var r=e(this);expand=e(''),expand.on("click",function(e){return t.toggleCurrent(r),e.stopPropagation(),!1}),r.prepend(expand)})},e.reset=function(){var e=encodeURI(window.location.hash);if(e)try{var t=$(".wy-menu-vertical").find('[href="'+e+'"]');$(".wy-menu-vertical li.toctree-l1 li.current").removeClass("current"),t.closest("li.toctree-l2").addClass("current"),t.closest("li.toctree-l3").addClass("current"),t.closest("li.toctree-l4").addClass("current")}catch(r){console.log("Error expanding nav for anchor",r)}},e.onScroll=function(){this.winScroll=!1;var e=this.win.scrollTop(),t=e+this.winHeight,r=this.navBar.scrollTop(),n=r+(e-this.winPosition);0>e||t>this.docHeight||(this.navBar.scrollTop(n),this.winPosition=e)},e.onResize=function(){this.winResize=!1,this.winHeight=this.win.height(),this.docHeight=$(document).height()},e.hashChange=function(){this.linkScroll=!0,this.win.one("hashchange",function(){this.linkScroll=!1})},e.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")},e}var o="undefined"!=typeof window?window.jQuery:e("jquery");t.exports.ThemeNav=n(),"undefined"!=typeof window&&(window.SphinxRtdTheme={StickyNav:t.exports.ThemeNav})},{jquery:"jquery"}],2:[function(e,t,r){function n(){var e=a.get(),t={project:e.project,version:e.version,page:e.page,theme:e.get_theme_name(),format:"jsonp"};"docroot"in e&&(t.docroot=e.docroot),"source_suffix"in e&&(t.source_suffix=e.source_suffix),0===window.location.pathname.indexOf("/projects/")&&(t.subproject=!0),$.ajax({url:e.api_host+"/api/v2/footer_html/",crossDomain:!0,xhrFields:{withCredentials:!0},dataType:"jsonp",data:t,success:function(e){s.init(e.version_compare),o(e),i()},error:function(){console.error("Error loading Read the Docs footer")}})}function o(e){var t=a.get();if(t.is_rtd_theme()?$("div.rst-other-versions").html(e.html):$("body").append(e.html),e.version_active?!e.version_supported:$(".rst-current-version").addClass("rst-out-of-date"),e.promo&&t.show_promo()){var r=new c.Promo(e.promo_data.id,e.promo_data.text,e.promo_data.link,e.promo_data.image);r&&r.display()}}function i(){function e(e){return/^(GET|HEAD|OPTIONS|TRACE)$/.test(e)}$.ajaxSetup({beforeSend:function(t,r){e(r.type)||t.setRequestHeader("X-CSRFToken",$("a.bookmark[token]").attr("token"))}})}var a=e("./rtd-data"),s=e("./version-compare"),c=e("../sponsorship");t.exports={init:n}},{"../sponsorship":9,"./rtd-data":4,"./version-compare":7}],3:[function(e,t,r){function n(){var e=o.get();if("builder"in e&&"mkdocs"==e.builder){$("").attr({type:"hidden",name:"project",value:e.project}).appendTo("#rtd-search-form"),$("").attr({type:"hidden",name:"version",value:e.version}).appendTo("#rtd-search-form"),$("").attr({type:"hidden",name:"type",value:"file"}).appendTo("#rtd-search-form"),$("#rtd-search-form").prop("action",e.api_host+"/search/");var t=$("nav.wy-nav-side:first"),r=$(window),n="stickynav",i=function(){t.height()<=r.height()?t.addClass(n):t.removeClass(n)};r.on("resize",i),i()}}var o=e("./rtd-data");t.exports={init:n}},{"./rtd-data":4}],4:[function(e,t,r){function n(){var e=Object.create(o),t={api_host:"https://readthedocs.org"};return $.extend(e,t,window.READTHEDOCS_DATA),e}var o={is_rtd_theme:function(){return"sphinx_rtd_theme"===this.get_theme_name()},is_sphinx_builder:function(){return!("builder"in this)||"mkdocs"!=this.builder},get_theme_name:function(){return"sphinx_rtd_theme"!==this.theme&&1===$("div.rst-other-versions").length?"sphinx_rtd_theme":this.theme},show_promo:function(){return"https://readthedocs.com"!==this.api_host&&this.is_sphinx_builder()&&this.is_rtd_theme()}};t.exports={get:n}},{}],5:[function(e,t,r){function n(){var e=i.get();o(e)}function o(e){var t=e.project,r=e.version,n=e.language||"en",o=e.api_host,i=e.subprojects||{},a=e.canonical_url||"/",s=function(e){var s=$.Deferred(),c=document.createElement("a");c.href=o,c.pathname="/api/v2/search",c.search="?q="+e+"&project="+t+"&version="+r+"&language="+n,s.then(function(r){var n=r.hits||{},o=n.hits||[];if(o.length)for(var s in o){var c=o[s],d=c.fields||{},l=$('
  • '),h=document.createElement("a"),u=c.highlight;if(h.href=a,d.project!=t){var p=i[d.project];h.href=p}if(h+=d.path+DOCUMENTATION_OPTIONS.FILE_SUFFIX,h.search="?highlight="+$.urlencode(e),l.append($("
    ").attr("href",h).html(d.title)),d.project!=t&&l.append($("").text(" (from project "+d.project+")")),u.content.length){var f=$('
    ').html(u.content[0]);f.find("em").addClass("highlighted"),l.append(f)}Search.output.append(l),l.slideDown(5)}Search.status.text(o.length?_("Search finished, found %s page(s) matching the search query.").replace("%s",o.length):_("Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories."))}).fail(function(t){Search.query_fallback(e)}).always(function(){$("#search-progress").empty(),Search.stopPulse(),Search.title.text(_("Search Results")),Search.status.fadeIn(500)}),$.ajax({url:c.href,complete:function(e,t){return"undefined"==typeof e.responseJSON||"undefined"==typeof e.responseJSON.results?s.reject():s.resolve(e.responseJSON.results)}}).error(function(e,t,r){return s.reject()})};"undefined"!=typeof Search&&(Search.query_fallback=Search.query,Search.query=s),$(document).ready(function(){"undefined"!=typeof Search&&Search.init()})}var i=e("./rtd-data");t.exports={init:n}},{"./rtd-data":4}],6:[function(e,t,r){function n(){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";_gaq&&_gaq.push(["rtfd._setAccount","UA-17997319-1"],["rtfd._trackEvent","Flyout","Click",e])}),!("builder"in e)||"builder"in e&&"mkdocs"!=e.builder){var t=i.ThemeNav;if($(document).ready(function(){setTimeout(function(){t.navBar||t.enable()},1e3)}),e.is_rtd_theme()){var r=jquery("div.wy-side-scroll:first");if(!r.length){var n=jquery("nav.wy-nav-side:first"),a=$("
    ").addClass("wy-side-scroll");n.children().detach().appendTo(a),a.prependTo(n),t.navBar=a}}}}var o=e("./rtd-data"),i=e("./../../../../../../bower_components/sphinx-rtd-theme/js/theme.js");t.exports={init:n}},{"./../../../../../../bower_components/sphinx-rtd-theme/js/theme.js":1,"./rtd-data":4}],7:[function(e,t,r){function n(e){var t=o.get();if(!e.is_highest){var r=window.location.pathname.replace(t.version,e.slug),n=$('');n.find("a").attr("href",r).text(e.version);var i=$("div.body");i.length||(i=$("div.document")),i.prepend(n)}}var o=e("./rtd-data");t.exports={init:n}},{"./rtd-data":4}],8:[function(e,t,r){var n=(e("./sponsorship"),e("./doc-embed/footer.js")),o=e("./doc-embed/mkdocs"),i=(e("./doc-embed/rtd-data"),e("./doc-embed/sphinx")),a=e("./doc-embed/search");$(document).ready(function(){n.init(),i.init(),o.init(),a.init()})},{"./doc-embed/footer.js":2,"./doc-embed/mkdocs":3,"./doc-embed/rtd-data":4,"./doc-embed/search":5,"./doc-embed/sphinx":6,"./sponsorship":9}],9:[function(e,t,r){function n(e,t,r,n){this.id=e,this.text=t,this.link=r,this.image=n,this.promo=null}t.exports={Promo:n},n.prototype.create=function(){function e(){_gaq&&_gaq.push(["rtfd._setAccount","UA-17997319-1"],["rtfd._trackEvent","Promo","Click",t.id])}var t=this,r=$("nav.wy-nav-side");if(r.length){promo=$("
    ").attr("class","wy-menu rst-pro");{var n=$("
    ").attr("class","rst-pro-about"),o=$("").attr("href","http://docs.readthedocs.org/en/latest/sponsors.html#sponsorship-information").appendTo(n);$("").attr("class","fa fa-info-circle").appendTo(o)}if(n.appendTo(promo),t.image){{var i=$("").attr("class","rst-pro-image-wrapper").attr("href",t.link).attr("target","_blank").on("click",e);$("").attr("class","rst-pro-image").attr("src",t.image).appendTo(i)}promo.append(i)}var a=$("").html(t.text);return $(a).find("a").each(function(){$(this).attr("class","rst-pro-link").attr("href",t.link).attr("target","_blank").on("click",e)}),promo.append(a),promo.appendTo(r),promo.wrapper=$("
    ").attr("class","rst-pro-wrapper").appendTo(r),promo}},n.prototype.display=function(){var e=this.promo;e||(e=this.promo=this.create()),e&&e.show()},n.prototype.disable=function(){},n.from_variants=function(e){if(0==e.length)return null;var t=Math.floor(Math.random()*e.length),r=e[t],o=r.text,i=r.link,a=r.image,s=r.id;return new n(s,o,i,a)}},{}]},{},[8]); \ No newline at end of file diff --git a/readthedocs/doc_builder/templates/doc_builder/conf.py.tmpl b/readthedocs/doc_builder/templates/doc_builder/conf.py.tmpl index 46ca3148ff7..fc7fffcd972 100644 --- a/readthedocs/doc_builder/templates/doc_builder/conf.py.tmpl +++ b/readthedocs/doc_builder/templates/doc_builder/conf.py.tmpl @@ -93,6 +93,9 @@ context = { 'downloads': [ {% for key, val in downloads.items %} ("{{ key }}", "{{ val }}"),{% endfor %} ], + 'subprojects': [ {% for slug, url in project.get_subproject_urls %} + ("{{ slug }}", "{{ url }}"),{% endfor %} + ], 'slug': '{{ project.slug }}', 'name': u'{{ project.name }}', 'rtd_language': u'{{ project.language }}', diff --git a/readthedocs/projects/models.py b/readthedocs/projects/models.py index 2b4d8418e6a..01ea63f12be 100644 --- a/readthedocs/projects/models.py +++ b/readthedocs/projects/models.py @@ -359,6 +359,22 @@ def get_canonical_url(self): else: return self.get_docs_url() + def get_subproject_urls(self): + """List subproject URLs + + This is used in search result linking + """ + if getattr(settings, 'DONT_HIT_DB', True): + return [(proj['slug'], proj['canonical_url']) + for proj in ( + apiv2.project(self.pk) + .subprojects() + .get()['subprojects'] + )] + else: + return [(proj.child.slug, proj.child.get_docs_url()) + for proj in self.subprojects.all()] + def get_production_media_path(self, type_, version_slug, include_file=True): """ This is used to see if these files exist so we can offer them for download. diff --git a/readthedocs/templates/sphinx/layout.html b/readthedocs/templates/sphinx/layout.html index 63c505b254f..50e7d590783 100644 --- a/readthedocs/templates/sphinx/layout.html +++ b/readthedocs/templates/sphinx/layout.html @@ -22,6 +22,7 @@ project: "{{ slug }}", version: "{{ current_version }}", language: "{{ rtd_language }}", + subprojects: {}, canonical_url: "{{ canonical_url }}", page: "{{ pagename }}", builder: "sphinx", @@ -35,6 +36,9 @@ api_host: "{{ api_host }}", commit: "{{ commit }}" }; + {% for slug, url in subprojects %} + READTHEDOCS_DATA.subprojects["{{ slug }}"] = "{{ url }}"; + {% endfor %} // Old variables var doc_version = "{{ current_version }}"; var doc_slug = "{{ slug }}"; From 5efc1213b3cf18ec210b1fa3156f98c85c0d0ca9 Mon Sep 17 00:00:00 2001 From: Anthony Johnson Date: Tue, 23 Feb 2016 14:55:38 -0800 Subject: [PATCH 5/5] Lint fix --- readthedocs/projects/models.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/readthedocs/projects/models.py b/readthedocs/projects/models.py index 01ea63f12be..4206a54c295 100644 --- a/readthedocs/projects/models.py +++ b/readthedocs/projects/models.py @@ -369,8 +369,7 @@ def get_subproject_urls(self): for proj in ( apiv2.project(self.pk) .subprojects() - .get()['subprojects'] - )] + .get()['subprojects'])] else: return [(proj.child.slug, proj.child.get_docs_url()) for proj in self.subprojects.all()]