Skip to content

Commit ecba0f1

Browse files
authored
Merge pull request #6867 from readthedocs/optimize-resolve-path
Optimize resolve_path
2 parents d5d753d + 71c4ca1 commit ecba0f1

File tree

2 files changed

+130
-51
lines changed

2 files changed

+130
-51
lines changed

readthedocs/core/resolver.py

Lines changed: 69 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -99,34 +99,18 @@ def resolve_path(
9999
cname=None,
100100
):
101101
"""Resolve a URL with a subset of fields defined."""
102-
cname = cname or project.get_canonical_custom_domain()
103102
version_slug = version_slug or project.get_default_version()
104103
language = language or project.language
105104

106105
filename = self._fix_filename(project, filename)
107106

108-
current_project = project
109-
project_slug = project.slug
110-
subproject_slug = None
111-
# We currently support more than 2 levels of nesting subprojects and
112-
# translations, only loop twice to avoid sticking in the loop
113-
for _ in range(0, 2):
114-
main_language_project = current_project.main_language_project
115-
relation = current_project.get_parent_relationship()
116-
117-
if main_language_project:
118-
current_project = main_language_project
119-
project_slug = main_language_project.slug
120-
language = project.language
121-
subproject_slug = None
122-
elif relation:
123-
current_project = relation.parent
124-
project_slug = relation.parent.slug
125-
subproject_slug = relation.alias
126-
cname = relation.parent.domains.filter(canonical=True).first()
127-
else:
128-
break
129-
107+
main_project, subproject_slug = self._get_canonical_project_data(project)
108+
project_slug = main_project.slug
109+
cname = (
110+
cname
111+
or self._use_subdomain()
112+
or main_project.get_canonical_custom_domain()
113+
)
130114
single_version = bool(project.single_version or single_version)
131115

132116
return self.base_resolve_path(
@@ -195,6 +179,68 @@ def resolve(
195179
)
196180
return urlunparse((protocol, domain, path, '', query_params, ''))
197181

182+
def _get_canonical_project_data(self, project):
183+
"""
184+
Returns a tuple with (project, subproject_slug) from the canonical project of `project`.
185+
186+
We currently support more than 2 levels of nesting subprojects and translations,
187+
but we only serve 2 levels to avoid sticking in the loop.
188+
This means, we can have the following cases:
189+
190+
- The project isn't a translation or subproject
191+
192+
We serve the documentation from the domain of the project itself
193+
(main.docs.com/).
194+
195+
- The project is a translation of a project
196+
197+
We serve the documentation from the domain of the main translation
198+
(main.docs.com/es/).
199+
200+
- The project is a subproject of a project
201+
202+
We serve the documentation from the domain of the super project
203+
(main.docs.com/projects/subproject/).
204+
205+
- The project is a translation, and the main translation is a subproject of a project, like:
206+
207+
- docs
208+
- api (subproject of ``docs``)
209+
- api-es (translation of ``api``, and current project to be served)
210+
211+
We serve the documentation from the domain of the super project
212+
(docs.docs.com/projects/api/es/).
213+
214+
- The project is a subproject, and the superproject is a translation of a project, like:
215+
216+
- docs
217+
- docs-es (translation of ``docs``)
218+
- api-es (subproject of ``docs-es``, and current project to be served)
219+
220+
We serve the documentation from the domain of the super project (the translation),
221+
this is docs-es.docs.com/projects/api-es/es/.
222+
We aren't going to support this case for now.
223+
224+
In summary: If the project is a subproject,
225+
we don't care if the superproject is a translation,
226+
we always serve from the domain of the superproject.
227+
If the project is a translation,
228+
we need to check if the main translation is a subproject.
229+
"""
230+
main_project = project
231+
subproject_slug = None
232+
233+
main_language_project = main_project.main_language_project
234+
if main_language_project:
235+
main_project = main_language_project
236+
237+
relation = main_project.get_parent_relationship()
238+
if relation:
239+
main_project = relation.parent
240+
subproject_slug = relation.alias
241+
242+
return (main_project, subproject_slug)
243+
198244
def _get_canonical_project(self, project, projects=None):
199245
"""
200246
Recursively get canonical project for subproject or translations.

readthedocs/rtd_tests/tests/test_resolver.py

Lines changed: 61 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
1-
import django_dynamic_fixture as fixture
21
from unittest import mock
2+
3+
import django_dynamic_fixture as fixture
4+
import pytest
35
from django.test import TestCase, override_settings
46

7+
from readthedocs.builds.constants import EXTERNAL
58
from readthedocs.core.resolver import (
69
Resolver,
710
resolve,
811
resolve_domain,
912
resolve_path,
1013
)
11-
from readthedocs.builds.constants import EXTERNAL
1214
from readthedocs.projects.constants import PRIVATE
1315
from readthedocs.projects.models import Domain, Project, ProjectRelationship
1416
from readthedocs.rtd_tests.utils import create_user
@@ -257,32 +259,6 @@ def test_resolver_force_language_version(self):
257259
)
258260
self.assertEqual(url, '/cz/foo/index.html')
259261

260-
def test_resolver_no_force_translation(self):
261-
with override_settings(USE_SUBDOMAIN=False):
262-
url = resolve_path(
263-
project=self.translation, filename='index.html', language='cz',
264-
)
265-
self.assertEqual(url, '/docs/pip/ja/latest/index.html')
266-
with override_settings(USE_SUBDOMAIN=True):
267-
url = resolve_path(
268-
project=self.translation, filename='index.html', language='cz',
269-
)
270-
self.assertEqual(url, '/ja/latest/index.html')
271-
272-
def test_resolver_no_force_translation_with_version(self):
273-
with override_settings(USE_SUBDOMAIN=False):
274-
url = resolve_path(
275-
project=self.translation, filename='index.html', language='cz',
276-
version_slug='foo',
277-
)
278-
self.assertEqual(url, '/docs/pip/ja/foo/index.html')
279-
with override_settings(USE_SUBDOMAIN=True):
280-
url = resolve_path(
281-
project=self.translation, filename='index.html', language='cz',
282-
version_slug='foo',
283-
)
284-
self.assertEqual(url, '/ja/foo/index.html')
285-
286262

287263
class ResolverCanonicalProject(TestCase):
288264

@@ -547,6 +523,63 @@ def test_resolver_translation(self):
547523
url = resolve(project=self.translation)
548524
self.assertEqual(url, 'http://pip.readthedocs.org/ja/latest/')
549525

526+
@override_settings(PRODUCTION_DOMAIN='readthedocs.org')
527+
def test_resolver_nested_translation_of_a_subproject(self):
528+
"""The project is a translation, and the main translation is a subproject of a project."""
529+
translation = fixture.get(
530+
Project,
531+
slug='api-es',
532+
language='es',
533+
users=[self.owner],
534+
main_language_project=self.subproject,
535+
)
536+
537+
with override_settings(USE_SUBDOMAIN=False):
538+
url = resolve(project=translation)
539+
self.assertEqual(
540+
url, 'http://readthedocs.org/docs/pip/projects/sub/es/latest/',
541+
)
542+
with override_settings(USE_SUBDOMAIN=True):
543+
url = resolve(project=translation)
544+
self.assertEqual(
545+
url, 'http://pip.readthedocs.org/projects/sub/es/latest/',
546+
)
547+
548+
@pytest.mark.xfail(reason='We do not support this for now', strict=True)
549+
@override_settings(PRODUCTION_DOMAIN='readthedocs.org')
550+
def test_resolver_nested_subproject_of_a_translation(self):
551+
"""The project is a subproject, and the superproject is a translation of a project."""
552+
project = fixture.get(
553+
Project,
554+
slug='all-docs',
555+
language='en',
556+
users=[self.owner],
557+
main_language_project=None,
558+
)
559+
translation = fixture.get(
560+
Project,
561+
slug='docs-es',
562+
language='es',
563+
users=[self.owner],
564+
main_language_project=project,
565+
)
566+
567+
subproject = fixture.get(
568+
Project,
569+
slug='api-es',
570+
language='es',
571+
users=[self.owner],
572+
main_language_project=None,
573+
)
574+
translation.add_subproject(subproject)
575+
576+
with override_settings(USE_SUBDOMAIN=False):
577+
url = resolve(project=subproject)
578+
self.assertEqual(url, 'http://readthedocs.org/docs/docs-es/projects/api-es/es/latest/')
579+
with override_settings(USE_SUBDOMAIN=True):
580+
url = resolve(project=subproject)
581+
self.assertEqual(url, 'http://docs-es.readthedocs.org/projects/api-es/es/latest/')
582+
550583
@override_settings(PRODUCTION_DOMAIN='readthedocs.org')
551584
def test_resolver_single_version(self):
552585
self.pip.single_version = True

0 commit comments

Comments
 (0)