Skip to content

Addons: make it work on invalid URLs #12372

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Aug 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 38 additions & 4 deletions readthedocs/proxito/tests/test_hosting.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,17 @@ def _get_response_dict(self, view_name, filepath=None):
return json.load(open(filename))

def _normalize_datetime_fields(self, obj):
obj["projects"]["current"]["created"] = "2019-04-29T10:00:00Z"
obj["projects"]["current"]["modified"] = "2019-04-29T12:00:00Z"
obj["builds"]["current"]["created"] = "2019-04-29T10:00:00Z"
obj["builds"]["current"]["finished"] = "2019-04-29T10:01:00Z"
try:
obj["projects"]["current"]["created"] = "2019-04-29T10:00:00Z"
obj["projects"]["current"]["modified"] = "2019-04-29T12:00:00Z"
except:
pass

try:
obj["builds"]["current"]["created"] = "2019-04-29T10:00:00Z"
obj["builds"]["current"]["finished"] = "2019-04-29T10:01:00Z"
except:
pass
return obj

def test_get_config_v1(self):
Expand Down Expand Up @@ -727,6 +734,33 @@ def test_send_project_version_slugs_and_url(self):
"v1"
)

def test_send_project_slug_and_notfound_version_slug(self):
r = self.client.get(
reverse("proxito_readthedocs_docs_addons"),
{
"api-version": "1.0.0",
"client-version": "0.6.0",
"project-slug": self.project.slug,
"version-slug": "not-found",
},
secure=True,
headers={
"host": "project.dev.readthedocs.io",
},
)
assert r.status_code == 200

expected_response = self._get_response_dict("v1")

# Since there is no version, there are some fields that we need to change from the default response
del expected_response["addons"]["doc_diff"]
expected_response["builds"]["current"] = None
expected_response["versions"]["current"] = None
expected_response["readthedocs"]["resolver"]["filename"] = None
expected_response["addons"]["search"]["default_filter"] = f"project:{self.project.slug}"
assert self._normalize_datetime_fields(r.json()) == expected_response


def test_custom_domain_url(self):
fixture.get(
Domain,
Expand Down
11 changes: 9 additions & 2 deletions readthedocs/proxito/views/hosting.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,10 @@ def _resolve_resources(self):
else:
# When not sending "url", we require "project-slug" and "version-slug".
project = get_object_or_404(Project, slug=project_slug)
version = get_object_or_404(project.versions.all(), slug=version_slug)
# We do allow hitting this URL without a valid version.
# This is the same case than sending `?url=` with a partial match
# (eg. invalid URL path).
version = project.versions.filter(slug=version_slug).first()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably check if the version slug is empty instead. Given a wrong version and not version at all isn't the same.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We aren't doing anything different if the version slug is invalid/empty when the ?url= attribute is passed either. This matches that behavior when using ?version= attribute.

I'm happy to discuss behaving differently in those cases if we want that, but I want to move forward with this PR here and work on that different behavior in a different one.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Opened #12420


# A project is always required.
if not project:
Expand Down Expand Up @@ -378,6 +381,10 @@ def _v1(self, project, version, build, filename, url, request):
project.addons.flyout_sorting_latest_stable_at_beginning,
)

search_default_filter = f"project:{project.slug}"
if version:
search_default_filter = f"project:{project.slug}/{version.slug}"

data = {
"api_version": "1",
"projects": self._get_projects_response(
Expand Down Expand Up @@ -478,7 +485,7 @@ def _v1(self, project, version, build, filename, url, request):
# f"subprojects:{project.slug}/{version.slug}",
# ],
],
"default_filter": f"project:{project.slug}/{version.slug}" if version else None,
"default_filter": search_default_filter,
},
"linkpreviews": {
"enabled": project.addons.linkpreviews_enabled,
Expand Down