Skip to content

Commit f238b3e

Browse files
authored
chore: update next be a complete string url (#4527)
1 parent 8bf269c commit f238b3e

File tree

2 files changed

+44
-25
lines changed

2 files changed

+44
-25
lines changed

course_discovery/apps/api/v2/tests/test_views/test_search.py

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
""" Test cases for api/v2/search/all """
1+
""" Test cases for api/v2/search/all endpoint """
22

33
import json
4+
from urllib.parse import parse_qs, urlparse
45

56
import ddt
67
from django.urls import reverse
@@ -17,11 +18,16 @@
1718
class AggregateSearchViewSetV2Tests(mixins.LoginMixin, ElasticsearchTestMixin, mixins.APITestCase):
1819
list_path = reverse("api:v2:search-all-list")
1920

20-
def fetch_page_data(self, page_size, search_after=None):
21-
query_params = {"page_size": page_size}
22-
if search_after:
23-
query_params["search_after"] = search_after
24-
response = self.client.get(self.list_path, data=query_params)
21+
def fetch_page_data(self, page_size, next_url=None):
22+
"""
23+
Fetch a page of data using the provided page size and next_url.
24+
If next_url is not provided, fetch the first page.
25+
"""
26+
if next_url:
27+
response = self.client.get(next_url)
28+
else:
29+
query_params = {"page_size": page_size}
30+
response = self.client.get(self.list_path, data=query_params)
2531
assert response.status_code == 200
2632
return response.json()
2733

@@ -96,20 +102,27 @@ def test_search_after_pagination(self):
96102
self.validate_page_data(response_data, page_size)
97103

98104
all_results = response_data["results"]
99-
next_token = response_data.get("next")
105+
next_url = response_data.get("next")
106+
107+
while next_url:
108+
# Parse the `search_after` value from the next_url query params
109+
parsed_url = urlparse(next_url)
110+
query_params = parse_qs(parsed_url.query)
111+
search_after = query_params.get("search_after", [None])[0]
112+
assert search_after is not None, "'search_after' parameter is missing in the next_url"
100113

101-
while next_token:
102-
response_data = self.fetch_page_data(page_size, search_after=json.dumps(next_token))
114+
last_sort_value = all_results[-1]["sort"]
115+
assert (
116+
json.loads(search_after) == last_sort_value
117+
), "The 'search_after' value in the next_url does not match the 'sort' field of the last result"
118+
119+
response_data = self.fetch_page_data(page_size, next_url=next_url)
103120

104121
expected_size = min(page_size, 75 - len(all_results))
105122
self.validate_page_data(response_data, expected_size)
106123

107124
all_results.extend(response_data["results"])
108-
next_token = response_data.get("next")
109-
110-
if next_token:
111-
last_sort_value = response_data["results"][-1]["sort"]
112-
assert last_sort_value == next_token
125+
next_url = response_data.get("next")
113126

114127
assert len(all_results) == 75, "The total number of results does not match the expected count"
115128

course_discovery/apps/edx_elasticsearch_dsl_extensions/viewsets.py

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from django_elasticsearch_dsl_drf.pagination import PageNumberPagination
66
from django_elasticsearch_dsl_drf.viewsets import DocumentViewSet as OriginDocumentViewSet
77
from rest_framework.permissions import IsAuthenticated
8+
from rest_framework.utils.urls import replace_query_param
89

910
from course_discovery.apps.api import mixins
1011
from course_discovery.apps.edx_elasticsearch_dsl_extensions.backends import MultiMatchSearchFilterBackend
@@ -107,13 +108,14 @@ class SearchAfterPagination(PageNumberPagination):
107108
"""
108109

109110
page_size_query_param = "page_size"
111+
search_after_param = "search_after"
110112

111113
def paginate_queryset(self, queryset, request, view=None):
112114
"""
113115
Paginate the Elasticsearch queryset using search_after.
114116
"""
115117

116-
search_after = request.query_params.get("search_after")
118+
search_after = request.query_params.get(self.search_after_param)
117119
if search_after:
118120
try:
119121
queryset = queryset.extra(search_after=json.loads(search_after))
@@ -122,16 +124,20 @@ def paginate_queryset(self, queryset, request, view=None):
122124

123125
return super().paginate_queryset(queryset, request, view)
124126

125-
def get_paginated_response(self, data):
126-
"""
127-
Get paginated response, including search_after value for the next page.
128-
"""
129-
response = super().get_paginated_response(data)
130-
last_item = data[-1] if data else None
131-
search_after = last_item.get("sort") if last_item else None
132-
next_link = response.data.pop("next", None)
133-
response.data["next"] = search_after if next_link else None
134-
return response
127+
def get_next_link(self):
128+
if not self.page.has_next():
129+
return None
130+
131+
last_item_sort = self._get_last_item_sort()
132+
if not last_item_sort:
133+
return None
134+
135+
url = self.request.build_absolute_uri()
136+
return replace_query_param(url, self.search_after_param, json.dumps(last_item_sort))
137+
138+
def _get_last_item_sort(self):
139+
last_item = self.page.object_list[-1] if self.page.object_list else None
140+
return list(last_item.meta.sort) if last_item else None
135141

136142

137143
class BaseElasticsearchDocumentViewSet(mixins.DetailMixin, mixins.FacetMixin, DocumentViewSet):

0 commit comments

Comments
 (0)