Skip to content

Commit 6239b5f

Browse files
authored
Proxito: redirect to main project from subprojects (#8187)
Closes #7881 We can revert #7880 after releasing this.
1 parent 4a18f9c commit 6239b5f

File tree

4 files changed

+101
-12
lines changed

4 files changed

+101
-12
lines changed

readthedocs/proxito/middleware.py

+10-4
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,16 @@
66
Additional processing is done to get the project from the URL in the ``views.py`` as well.
77
"""
88
import logging
9-
import sys
109
import re
10+
import sys
1111
from urllib.parse import urlparse
1212

1313
from django.conf import settings
14-
from django.shortcuts import render, redirect
15-
from django.utils.deprecation import MiddlewareMixin
14+
from django.shortcuts import redirect, render
1615
from django.urls import reverse
16+
from django.utils.deprecation import MiddlewareMixin
1717

18-
from readthedocs.projects.models import Domain, Project
18+
from readthedocs.projects.models import Domain, Project, ProjectRelationship
1919

2020
log = logging.getLogger(__name__) # noqa
2121

@@ -66,6 +66,12 @@ def map_host_to_project_slug(request): # pylint: disable=too-many-return-statem
6666
).exists():
6767
log.debug('Proxito Public Domain -> Canonical Domain Redirect: host=%s', host)
6868
request.canonicalize = 'canonical-cname'
69+
elif (
70+
ProjectRelationship.objects.
71+
filter(child__slug=project_slug).exists()
72+
):
73+
log.debug('Proxito Public Domain -> Subproject Main Domain Redirect: host=%s', host)
74+
request.canonicalize = 'subproject-main-domain'
6975
return project_slug
7076

7177
# TODO: This can catch some possibly valid domains (docs.readthedocs.io.com) for example

readthedocs/proxito/tests/test_middleware.py

+36
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,42 @@ def test_canonical_cname_redirect(self):
7373
self.assertTrue(hasattr(request, 'canonicalize'))
7474
self.assertEqual(request.canonicalize, 'canonical-cname')
7575

76+
def test_subproject_redirect(self):
77+
"""Requests to a subproject should redirect to the domain of the main project."""
78+
subproject = get(
79+
Project,
80+
name='subproject',
81+
slug='subproject',
82+
users=[self.owner],
83+
privacy_level=PUBLIC,
84+
)
85+
subproject.versions.update(privacy_level=PUBLIC)
86+
get(
87+
ProjectRelationship,
88+
parent=self.pip,
89+
child=subproject,
90+
)
91+
92+
for url in (self.url, '/subdir/', '/en/latest/'):
93+
request = self.request(url, HTTP_HOST='subproject.dev.readthedocs.io')
94+
res = self.run_middleware(request)
95+
self.assertIsNone(res)
96+
self.assertEqual(getattr(request, 'canonicalize', None), 'subproject-main-domain')
97+
98+
# Using a custom domain in a subproject isn't supported (or shouldn't be!).
99+
cname = 'docs.random.com'
100+
domain = get(
101+
Domain,
102+
project=subproject,
103+
domain=cname,
104+
canonical=True,
105+
https=True,
106+
)
107+
request = self.request(self.url, HTTP_HOST='subproject.dev.readthedocs.io')
108+
res = self.run_middleware(request)
109+
self.assertIsNone(res)
110+
self.assertEqual(getattr(request, 'canonicalize', None), 'canonical-cname')
111+
76112
# We are not canonicalizing custom domains -> public domain for now
77113
@pytest.mark.xfail(strict=True)
78114
def test_canonical_cname_redirect_public_domain(self):

readthedocs/proxito/tests/test_redirects.py

+51
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,57 @@ def test_single_version_subproject_root_url_no_slash(self):
5555
r['Location'], 'https://project.dev.readthedocs.io/projects/subproject/',
5656
)
5757

58+
def test_subproject_redirect(self):
59+
r = self.client.get('/', HTTP_HOST='subproject.dev.readthedocs.io')
60+
self.assertEqual(r.status_code, 302)
61+
self.assertEqual(
62+
r['Location'], 'https://project.dev.readthedocs.io/projects/subproject/en/latest/',
63+
)
64+
65+
r = self.client.get('/en/latest/', HTTP_HOST='subproject.dev.readthedocs.io')
66+
self.assertEqual(r.status_code, 302)
67+
self.assertEqual(
68+
r['Location'], 'https://project.dev.readthedocs.io/projects/subproject/en/latest/',
69+
)
70+
71+
r = self.client.get('/en/latest/foo/bar', HTTP_HOST='subproject.dev.readthedocs.io')
72+
self.assertEqual(r.status_code, 302)
73+
self.assertEqual(
74+
r['Location'], 'https://project.dev.readthedocs.io/projects/subproject/en/latest/foo/bar',
75+
)
76+
77+
self.domain.canonical = True
78+
self.domain.save()
79+
r = self.client.get('/en/latest/foo/bar', HTTP_HOST='subproject.dev.readthedocs.io')
80+
self.assertEqual(r.status_code, 302)
81+
self.assertEqual(
82+
r['Location'], 'https://docs1.example.com/projects/subproject/en/latest/foo/bar',
83+
)
84+
85+
def test_single_version_subproject_redirect(self):
86+
self.subproject.single_version = True
87+
self.subproject.save()
88+
89+
r = self.client.get('/', HTTP_HOST='subproject.dev.readthedocs.io')
90+
self.assertEqual(r.status_code, 302)
91+
self.assertEqual(
92+
r['Location'], 'https://project.dev.readthedocs.io/projects/subproject/',
93+
)
94+
95+
r = self.client.get('/foo/bar/', HTTP_HOST='subproject.dev.readthedocs.io')
96+
self.assertEqual(r.status_code, 302)
97+
self.assertEqual(
98+
r['Location'], 'https://project.dev.readthedocs.io/projects/subproject/foo/bar/',
99+
)
100+
101+
self.domain.canonical = True
102+
self.domain.save()
103+
r = self.client.get('/foo/bar', HTTP_HOST='subproject.dev.readthedocs.io')
104+
self.assertEqual(r.status_code, 302)
105+
self.assertEqual(
106+
r['Location'], 'https://docs1.example.com/projects/subproject/foo/bar',
107+
)
108+
58109
def test_root_redirect_with_query_params(self):
59110
r = self.client.get('/?foo=bar', HTTP_HOST='project.dev.readthedocs.io')
60111
self.assertEqual(r.status_code, 302)

readthedocs/rtd_tests/tests/test_footer.py

+4-8
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,12 @@
77
from django_dynamic_fixture import get
88
from rest_framework.test import APIRequestFactory
99

10-
from readthedocs.api.v2.views.footer_views import (
11-
FooterHTML,
12-
get_version_compare_data,
13-
)
10+
from readthedocs.api.v2.views.footer_views import get_version_compare_data
1411
from readthedocs.builds.constants import BRANCH, EXTERNAL, LATEST, TAG
1512
from readthedocs.builds.models import Version
1613
from readthedocs.core.middleware import ReadTheDocsSessionMiddleware
1714
from readthedocs.projects.constants import PUBLIC
18-
from readthedocs.projects.models import Feature, Project
15+
from readthedocs.projects.models import Project
1916

2017

2118
class BaseTestFooterHTML:
@@ -427,7 +424,7 @@ def test_highest_version_without_tags(self):
427424
class TestFooterPerformance(TestCase):
428425
# The expected number of queries for generating the footer
429426
# This shouldn't increase unless we modify the footer API
430-
EXPECTED_QUERIES = 14
427+
EXPECTED_QUERIES = 15
431428

432429
def setUp(self):
433430
self.pip = get(
@@ -485,7 +482,6 @@ def test_domain_queries(self):
485482
canonical=True,
486483
)
487484

488-
# Setting up a custom domain increases only one query.
489-
with self.assertNumQueries(self.EXPECTED_QUERIES + 1):
485+
with self.assertNumQueries(self.EXPECTED_QUERIES):
490486
response = self.client.get(self.url, HTTP_HOST=domain)
491487
self.assertContains(response, domain)

0 commit comments

Comments
 (0)