Skip to content

Commit 2c6a064

Browse files
authored
Proxito: allow to generate proxied API URLs with a prefix (#10634)
- Ref #10181 - Ref #10408 - Ref readthedocs/meta#124
1 parent 012c053 commit 2c6a064

File tree

4 files changed

+61
-22
lines changed

4 files changed

+61
-22
lines changed

readthedocs/api/v2/serializers.py

+15-14
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,21 @@ class ProjectSerializer(serializers.ModelSerializer):
1717
class Meta:
1818
model = Project
1919
fields = (
20-
'id',
21-
'name',
22-
'slug',
23-
'description',
24-
'language',
25-
'programming_language',
26-
'repo',
27-
'repo_type',
28-
'default_version',
29-
'default_branch',
30-
'documentation_type',
31-
'users',
32-
'canonical_url',
33-
'urlconf',
20+
"id",
21+
"name",
22+
"slug",
23+
"description",
24+
"language",
25+
"programming_language",
26+
"repo",
27+
"repo_type",
28+
"default_version",
29+
"default_branch",
30+
"documentation_type",
31+
"users",
32+
"canonical_url",
33+
"urlconf",
34+
"custom_prefix",
3435
)
3536

3637

readthedocs/projects/models.py

+22-7
Original file line numberDiff line numberDiff line change
@@ -693,11 +693,9 @@ def proxied_api_host(self):
693693
This needs to start with a slash at the root of the domain,
694694
and end without a slash
695695
"""
696-
if self.urlconf:
697-
# Add our proxied api host at the first place we have a $variable
698-
# This supports both subpaths & normal root hosting
699-
path_prefix = self.custom_path_prefix
700-
return unsafe_join_url_path(path_prefix, "/_")
696+
custom_prefix = self.proxied_api_prefix
697+
if custom_prefix:
698+
return unsafe_join_url_path(custom_prefix, "/_")
701699
return '/_'
702700

703701
@property
@@ -715,12 +713,22 @@ def proxied_static_path(self):
715713
return f"{self.proxied_api_host}/static/"
716714

717715
@property
718-
def custom_path_prefix(self):
716+
def proxied_api_prefix(self):
719717
"""
720-
Get the path prefix from the custom urlconf.
718+
Get the path prefix for proxied API paths (``/_/``).
721719
722720
Returns `None` if the project doesn't have a custom urlconf.
723721
"""
722+
# When using a custom prefix, we can only handle serving
723+
# docs pages under the prefix, not special paths like `/_/`.
724+
# Projects using the old implementation, need to proxy `/_/`
725+
# paths as is, this is, without the prefix, while those projects
726+
# migrate to the new implementation, we will prefix special paths
727+
# when generating links, these paths will be manually un-prefixed in nginx.
728+
if self.custom_prefix and self.has_feature(
729+
Feature.USE_PROXIED_APIS_WITH_PREFIX
730+
):
731+
return self.custom_prefix
724732
if self.urlconf:
725733
# Return the value before the first defined variable,
726734
# as that is a prefix and not part of our normal doc patterns.
@@ -1930,6 +1938,7 @@ def add_features(sender, **kwargs):
19301938
DISABLE_PAGEVIEWS = "disable_pageviews"
19311939
RESOLVE_PROJECT_FROM_HEADER = "resolve_project_from_header"
19321940
USE_UNRESOLVER_WITH_PROXITO = "use_unresolver_with_proxito"
1941+
USE_PROXIED_APIS_WITH_PREFIX = "use_proxied_apis_with_prefix"
19331942
ALLOW_VERSION_WARNING_BANNER = "allow_version_warning_banner"
19341943

19351944
# Versions sync related features
@@ -2020,6 +2029,12 @@ def add_features(sender, **kwargs):
20202029
"Proxito: Use new unresolver implementation for serving documentation files."
20212030
),
20222031
),
2032+
(
2033+
USE_PROXIED_APIS_WITH_PREFIX,
2034+
_(
2035+
"Proxito: Use proxied APIs (/_/*) with the custom prefix if the project has one (Project.custom_prefix)."
2036+
),
2037+
),
20232038
(
20242039
ALLOW_VERSION_WARNING_BANNER,
20252040
_("Dashboard: Allow project to use the version warning banner."),

readthedocs/projects/tests/test_models.py

+23-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from django.test import TestCase
44
from django_dynamic_fixture import get
55

6-
from readthedocs.projects.models import Project
6+
from readthedocs.projects.models import Feature, Project
77

88

99
class TestURLPatternsUtils(TestCase):
@@ -115,3 +115,25 @@ def test_overlapping_prefixes(self):
115115
with pytest.raises(ValidationError) as excinfo:
116116
self.project.clean()
117117
self.assertEqual(excinfo.value.code, "ambiguous_path")
118+
119+
def test_proxied_api_prefix(self):
120+
self.assertEqual(self.project.custom_prefix, None)
121+
self.assertEqual(self.project.proxied_api_url, "_/")
122+
self.assertEqual(self.project.proxied_api_host, "/_")
123+
self.assertEqual(self.project.proxied_api_prefix, None)
124+
125+
self.project.custom_prefix = "/prefix/"
126+
self.project.save()
127+
128+
self.assertEqual(self.project.proxied_api_url, "_/")
129+
self.assertEqual(self.project.proxied_api_host, "/_")
130+
self.assertEqual(self.project.proxied_api_prefix, None)
131+
132+
get(
133+
Feature,
134+
projects=[self.project],
135+
feature_id=Feature.USE_PROXIED_APIS_WITH_PREFIX,
136+
)
137+
self.assertEqual(self.project.proxied_api_url, "prefix/_/")
138+
self.assertEqual(self.project.proxied_api_host, "/prefix/_")
139+
self.assertEqual(self.project.proxied_api_prefix, "/prefix/")

readthedocs/rtd_tests/tests/test_api.py

+1
Original file line numberDiff line numberDiff line change
@@ -3176,6 +3176,7 @@ def test_get_version_by_id(self):
31763176
"use_system_packages": False,
31773177
"users": [1],
31783178
"urlconf": None,
3179+
"custom_prefix": None,
31793180
},
31803181
"privacy_level": "public",
31813182
"downloads": {},

0 commit comments

Comments
 (0)