diff --git a/course_discovery/apps/api/v1/tests/test_views/test_search.py b/course_discovery/apps/api/v1/tests/test_views/test_search.py index 3bd5b70a71..b477cc7324 100644 --- a/course_discovery/apps/api/v1/tests/test_views/test_search.py +++ b/course_discovery/apps/api/v1/tests/test_views/test_search.py @@ -763,28 +763,28 @@ def test_results_include_aggregation_key(self): ) assert expected == actual - @ddt.data(True, False) - def test_learner_pathway_feature_flag(self, include_learner_pathways): + @ddt.data((True, 10, 8), (False, 0, 4)) + @ddt.unpack + def test_learner_pathway_feature_flag(self, include_learner_pathways, expected_result_count, expected_query_count): """ Verify the include_learner_pathways feature flag works as expected.""" - LearnerPathwayStepFactory(pathway__partner=self.partner) + LearnerPathwayStepFactory.create_batch(10, pathway__partner=self.partner) pathways = LearnerPathway.objects.all() - assert pathways.count() == 1 + assert pathways.count() == 10 query = { 'include_learner_pathways': include_learner_pathways, } - response = self.get_response( - query, - self.list_path - ) + with self.assertNumQueries(expected_query_count): + response = self.get_response(query, self.list_path) + assert response.status_code == 200 response_data = response.json() + assert response_data['count'] == expected_result_count + if include_learner_pathways: - assert response_data['count'] == 1 - assert response_data['results'][0] == self.serialize_learner_pathway_search(pathways[0]) - else: - assert response_data['count'] == 0 + for pathway in pathways: + assert self.serialize_learner_pathway_search(pathway) in response.data['results'] class LimitedAggregateSearchViewSetTests( diff --git a/course_discovery/apps/course_metadata/search_indexes/documents/learner_pathway.py b/course_discovery/apps/course_metadata/search_indexes/documents/learner_pathway.py index bda4f549f2..0650355058 100644 --- a/course_discovery/apps/course_metadata/search_indexes/documents/learner_pathway.py +++ b/course_discovery/apps/course_metadata/search_indexes/documents/learner_pathway.py @@ -1,6 +1,9 @@ from django.conf import settings +from django.db.models import Prefetch from django_elasticsearch_dsl import Index, fields +from course_discovery.apps.course_metadata.choices import CourseRunStatus +from course_discovery.apps.course_metadata.models import CourseRun from course_discovery.apps.learner_pathway.choices import PathwayStatus from course_discovery.apps.learner_pathway.models import LearnerPathway @@ -50,10 +53,26 @@ def prepare_partner(self, obj): def prepare_published(self, obj): return obj.status == PathwayStatus.Active - def get_queryset(self, excluded_restriction_types=None): # pylint: disable=unused-argument + def get_queryset(self, excluded_restriction_types=None): + if excluded_restriction_types is None: + excluded_restriction_types = [] + + course_runs = CourseRun.objects.filter( + status=CourseRunStatus.Published + ).exclude( + restricted_run__restriction_type__in=excluded_restriction_types + ) + return super().get_queryset().prefetch_related( - 'steps', 'steps__learnerpathwaycourse_set', 'steps__learnerpathwayprogram_set', - 'steps__learnerpathwayblock_set', + 'steps', + Prefetch( + 'steps__learnerpathwaycourse_set__course__course_runs', + queryset=course_runs + ), + Prefetch( + 'steps__learnerpathwayprogram_set__program__courses__course_runs', + queryset=course_runs + ) ) def prepare_skill_names(self, obj): diff --git a/course_discovery/apps/learner_pathway/api/serializers.py b/course_discovery/apps/learner_pathway/api/serializers.py index a33724b831..c6f191ecb8 100644 --- a/course_discovery/apps/learner_pathway/api/serializers.py +++ b/course_discovery/apps/learner_pathway/api/serializers.py @@ -3,8 +3,6 @@ """ from rest_framework import serializers -from course_discovery.apps.api.utils import get_excluded_restriction_types -from course_discovery.apps.course_metadata.choices import CourseRunStatus from course_discovery.apps.learner_pathway import models @@ -20,12 +18,7 @@ class Meta: fields = ('key', 'course_runs') def get_course_runs(self, obj): - excluded_restriction_types = get_excluded_restriction_types(self.context['request']) - return list(obj.course.course_runs.filter( - status=CourseRunStatus.Published - ).exclude( - restricted_run__restriction_type__in=excluded_restriction_types - ).values('key')) + return [{'key': course_run.key} for course_run in obj.course.course_runs.all()] class LearnerPathwayCourseSerializer(LearnerPathwayCourseMinimalSerializer): @@ -87,8 +80,7 @@ def get_card_image_url(self, step): return program.card_image_url def get_courses(self, obj): - excluded_restriction_types = get_excluded_restriction_types(self.context['request']) - return obj.get_linked_courses_and_course_runs(excluded_restriction_types=excluded_restriction_types) + return obj.get_linked_courses_and_course_runs() class LearnerPathwayBlockSerializer(serializers.ModelSerializer): diff --git a/course_discovery/apps/learner_pathway/api/v1/tests/test_views.py b/course_discovery/apps/learner_pathway/api/v1/tests/test_views.py index 2e3da2f3cc..546e5c1d3e 100644 --- a/course_discovery/apps/learner_pathway/api/v1/tests/test_views.py +++ b/course_discovery/apps/learner_pathway/api/v1/tests/test_views.py @@ -6,7 +6,9 @@ from rest_framework import status from course_discovery.apps.core.tests.factories import UserFactory -from course_discovery.apps.course_metadata.tests.factories import CourseRunFactory, RestrictedCourseRunFactory +from course_discovery.apps.course_metadata.tests.factories import ( + CourseFactory, CourseRunFactory, RestrictedCourseRunFactory +) from course_discovery.apps.learner_pathway.choices import PathwayStatus from course_discovery.apps.learner_pathway.tests.factories import ( LearnerPathwayCourseFactory, LearnerPathwayFactory, LearnerPathwayProgramFactory, LearnerPathwayStepFactory @@ -42,7 +44,17 @@ 'uuid': '1f301a72-f344-4a31-9e9a-e0b04d8d86b2', 'title': 'test-program-0', 'short_description': 'into to more basics', - 'content_type': 'program' + 'content_type': 'program', + 'courses': [ + { + 'key': 'BB+BB102', + 'course_runs': [ + { + 'key': 'course-v1:BB+BB102+1T2024', + } + ] + } + ], } ] } @@ -54,7 +66,6 @@ LEARNER_PATHWAY_PROGRAM_UUID = LEARNER_PATHWAY_DATA['steps'][0]['programs'][0]['uuid'] -@mark.django_db @ddt.ddt class TestLearnerPathwayViewSet(TestCase): """ @@ -93,11 +104,20 @@ def setUp(self): key=LEARNER_PATHWAY_DATA['steps'][0]['courses'][0]['course_runs'][0]['key'], status='published', ) + self.learner_pathway_program_course = CourseFactory( + key=LEARNER_PATHWAY_DATA['steps'][0]['programs'][0]['courses'][0]['key'] + ) LearnerPathwayProgramFactory( step=learner_pathway_step, program__uuid=LEARNER_PATHWAY_DATA['steps'][0]['programs'][0]['uuid'], program__title=LEARNER_PATHWAY_DATA['steps'][0]['programs'][0]['title'], program__subtitle=LEARNER_PATHWAY_DATA['steps'][0]['programs'][0]['short_description'], + program__courses=[self.learner_pathway_program_course], + ) + CourseRunFactory( + course=self.learner_pathway_program_course, + key=LEARNER_PATHWAY_DATA['steps'][0]['programs'][0]['courses'][0]['course_runs'][0]['key'], + status='published', ) self.client.login(username=self.user.username, password=USER_PASSWORD) @@ -245,3 +265,280 @@ def test_learner_pathway_uuids_endpoint(self, query_params, response): learner_pathway_uuids_url = f'/api/v1/learner-pathway/uuids/?{urlencode(query_params)}' api_response = self.client.get(learner_pathway_uuids_url) assert api_response.json() == response + + +@ddt.ddt +class TestLearnerPathwayStepViewSet(TestCase): + """ + Tests for LearnerPathwayStepViewSet. + """ + def setUp(self): + super().setUp() + self.user = UserFactory.create(is_staff=True, is_active=True) + self.user.set_password(USER_PASSWORD) + self.user.save() + self.client = Client() + + self.learner_pathway = LearnerPathwayFactory( + uuid=LEARNER_PATHWAY_DATA['uuid'], + title=LEARNER_PATHWAY_DATA['title'], + status=LEARNER_PATHWAY_DATA['status'], + overview=LEARNER_PATHWAY_DATA['overview'], + ) + self.learner_pathway_step = LearnerPathwayStepFactory( + pathway=self.learner_pathway, + uuid=LEARNER_PATHWAY_DATA['steps'][0]['uuid'], + min_requirement=LEARNER_PATHWAY_DATA['steps'][0]['min_requirement'], + ) + self.learner_pathway_course = LearnerPathwayCourseFactory( + step=self.learner_pathway_step, + course__key=LEARNER_PATHWAY_DATA['steps'][0]['courses'][0]['key'], + course__title=LEARNER_PATHWAY_DATA['steps'][0]['courses'][0]['title'], + course__short_description=LEARNER_PATHWAY_DATA['steps'][0]['courses'][0]['short_description'], + ) + self.learner_pathway_course_run = CourseRunFactory( + course=self.learner_pathway_course.course, + key=LEARNER_PATHWAY_DATA['steps'][0]['courses'][0]['course_runs'][0]['key'], + status='published', + ) + self.learner_pathway_program_course = CourseFactory( + key=LEARNER_PATHWAY_DATA['steps'][0]['programs'][0]['courses'][0]['key'] + ) + LearnerPathwayProgramFactory( + step=self.learner_pathway_step, + program__uuid=LEARNER_PATHWAY_DATA['steps'][0]['programs'][0]['uuid'], + program__title=LEARNER_PATHWAY_DATA['steps'][0]['programs'][0]['title'], + program__subtitle=LEARNER_PATHWAY_DATA['steps'][0]['programs'][0]['short_description'], + program__courses=[self.learner_pathway_program_course], + ) + CourseRunFactory( + course=self.learner_pathway_program_course, + key=LEARNER_PATHWAY_DATA['steps'][0]['programs'][0]['courses'][0]['course_runs'][0]['key'], + status='published', + ) + + self.client.login(username=self.user.username, password=USER_PASSWORD) + + self.view_url = f'/api/v1/learner-pathway-step/{self.learner_pathway_step.uuid}/' + + def _verify_learner_pathway_step_data(self, api_response, expected_data): + """ + Verify that learner pathway step api response matches the expected data. + """ + data = api_response.json() + + # Verify learner pathway step data + assert data['uuid'] == expected_data['uuid'] + assert data['min_requirement'] == expected_data['min_requirement'] + + # Verify associated course data + api_response_course = data['courses'][0] + expected_course_data = expected_data['courses'][0] + for key, value in expected_course_data.items(): + assert api_response_course[key] == value + + # Verify course run data (published course runs) + assert api_response_course['course_runs'][0]['key'] == expected_course_data['course_runs'][0]['key'] + + # Verify program data + api_response_program = data['programs'][0] + expected_program_data = expected_data['programs'][0] + for key, value in expected_program_data.items(): + assert api_response_program[key] == value + + def test_learner_pathway_step_api(self): + """ + Verify that learner pathway step api returns the expected response. + """ + api_response = self.client.get(self.view_url) + self._verify_learner_pathway_step_data(api_response, LEARNER_PATHWAY_DATA['steps'][0]) + + @ddt.data([True, 2], [False, 1]) + @ddt.unpack + def test_learner_pathway_step_restricted_runs(self, add_restriction_param, expected_run_count): + """ + Verify that restricted course runs are handled correctly based on query parameters. + """ + restricted_run = CourseRunFactory( + course=self.learner_pathway_course.course, + key='course-v1:AA+AA101+3T2024', + status='published', + ) + RestrictedCourseRunFactory(course_run=restricted_run, restriction_type='custom-b2c') + + url = f'/api/v1/learner-pathway-step/{self.learner_pathway_step.uuid}/' + if add_restriction_param: + url += '?include_restricted=custom-b2c' + + api_response = self.client.get(url) + data = api_response.json() + assert len(data['courses'][0]['course_runs']) == expected_run_count + + +@ddt.ddt +class TestLearnerPathwayCourseViewSet(TestCase): + """ + Tests for LearnerPathwayCourseViewSet. + """ + def setUp(self): + super().setUp() + self.user = UserFactory.create(is_staff=True, is_active=True) + self.user.set_password(USER_PASSWORD) + self.user.save() + self.client = Client() + + self.learner_pathway = LearnerPathwayFactory( + uuid=LEARNER_PATHWAY_DATA['uuid'], + title=LEARNER_PATHWAY_DATA['title'], + status=LEARNER_PATHWAY_DATA['status'], + overview=LEARNER_PATHWAY_DATA['overview'], + ) + self.learner_pathway_step = LearnerPathwayStepFactory( + pathway=self.learner_pathway, + uuid=LEARNER_PATHWAY_DATA['steps'][0]['uuid'], + min_requirement=LEARNER_PATHWAY_DATA['steps'][0]['min_requirement'], + ) + self.learner_pathway_course = LearnerPathwayCourseFactory( + step=self.learner_pathway_step, + course__key=LEARNER_PATHWAY_DATA['steps'][0]['courses'][0]['key'], + course__title=LEARNER_PATHWAY_DATA['steps'][0]['courses'][0]['title'], + course__short_description=LEARNER_PATHWAY_DATA['steps'][0]['courses'][0]['short_description'], + ) + self.learner_pathway_course_run = CourseRunFactory( + course=self.learner_pathway_course.course, + key=LEARNER_PATHWAY_DATA['steps'][0]['courses'][0]['course_runs'][0]['key'], + status='published', + ) + + self.client.login(username=self.user.username, password=USER_PASSWORD) + + self.view_url = f'/api/v1/learner-pathway-course/{self.learner_pathway_course.uuid}/' + + def _verify_learner_pathway_course_data(self, api_response, expected_data): + """ + Verify that learner pathway course api response matches the expected data. + """ + data = api_response.json() + + # Verify course data + assert data['key'] == expected_data['key'] + assert data['title'] == expected_data['title'] + assert data['short_description'] == expected_data['short_description'] + + # Verify course run data (published course runs) + api_response_course_run = data['course_runs'][0] + assert api_response_course_run['key'] == expected_data['course_runs'][0]['key'] + + def test_learner_pathway_course_api(self): + """ + Verify that learner pathway course api returns the expected response. + """ + api_response = self.client.get(self.view_url) + self._verify_learner_pathway_course_data(api_response, LEARNER_PATHWAY_DATA['steps'][0]['courses'][0]) + + @ddt.data([True, 2], [False, 1]) + @ddt.unpack + def test_learner_pathway_course_restricted_runs(self, add_restriction_param, expected_run_count): + """ + Verify that restricted course runs are handled correctly based on query parameters. + """ + restricted_run = CourseRunFactory( + course=self.learner_pathway_course.course, + key='course-v1:AA+AA101+3T2024', + status='published', + ) + RestrictedCourseRunFactory(course_run=restricted_run, restriction_type='custom-b2c') + + url = f'/api/v1/learner-pathway-course/{self.learner_pathway_course.uuid}/' + if add_restriction_param: + url += '?include_restricted=custom-b2c' + + api_response = self.client.get(url) + data = api_response.json() + assert len(data['course_runs']) == expected_run_count + + +@ddt.ddt +class TestLearnerPathwayProgramViewSet(TestCase): + """ + Tests for LearnerPathwayProgramViewSet. + """ + def setUp(self): + super().setUp() + self.user = UserFactory.create(is_staff=True, is_active=True) + self.user.set_password(USER_PASSWORD) + self.user.save() + self.client = Client() + + self.learner_pathway = LearnerPathwayFactory( + uuid=LEARNER_PATHWAY_DATA['uuid'], + title=LEARNER_PATHWAY_DATA['title'], + status=LEARNER_PATHWAY_DATA['status'], + overview=LEARNER_PATHWAY_DATA['overview'], + ) + self.learner_pathway_step = LearnerPathwayStepFactory( + pathway=self.learner_pathway, + uuid=LEARNER_PATHWAY_DATA['steps'][0]['uuid'], + min_requirement=LEARNER_PATHWAY_DATA['steps'][0]['min_requirement'], + ) + self.learner_pathway_program_course = CourseFactory( + key=LEARNER_PATHWAY_DATA['steps'][0]['programs'][0]['courses'][0]['key'] + ) + self.learner_pathway_program = LearnerPathwayProgramFactory( + step=self.learner_pathway_step, + program__uuid=LEARNER_PATHWAY_DATA['steps'][0]['programs'][0]['uuid'], + program__title=LEARNER_PATHWAY_DATA['steps'][0]['programs'][0]['title'], + program__subtitle=LEARNER_PATHWAY_DATA['steps'][0]['programs'][0]['short_description'], + program__courses=[self.learner_pathway_program_course] + ) + CourseRunFactory( + course=self.learner_pathway_program_course, + key=LEARNER_PATHWAY_DATA['steps'][0]['programs'][0]['courses'][0]['course_runs'][0]['key'], + status='published', + ) + + self.client.login(username=self.user.username, password=USER_PASSWORD) + + self.view_url = f'/api/v1/learner-pathway-program/{self.learner_pathway_program.uuid}/' + + def _verify_learner_pathway_program_data(self, api_response, expected_data): + """ + Verify that learner pathway program api response matches the expected data. + """ + data = api_response.json() + # Verify program data + assert data['uuid'] == expected_data['uuid'] + assert data['title'] == expected_data['title'] + assert data['short_description'] == expected_data['short_description'] + + # Verify course run data (published course runs) + api_response_course_run = data['courses'][0]['course_runs'][0] + assert api_response_course_run['key'] == expected_data['courses'][0]['course_runs'][0]['key'] + + def test_learner_pathway_program_api(self): + """ + Verify that learner pathway program api returns the expected response. + """ + api_response = self.client.get(self.view_url) + self._verify_learner_pathway_program_data(api_response, LEARNER_PATHWAY_DATA['steps'][0]['programs'][0]) + + @ddt.data([True, 2], [False, 1]) + @ddt.unpack + def test_learner_pathway_program_restricted_runs(self, add_restriction_param, expected_run_count): + """ + Verify that restricted program course runs are handled correctly based on query parameters. + """ + restricted_run = CourseRunFactory( + course=self.learner_pathway_program.program.courses.first(), + key='course-v1:AA+AA101+3T2024', + status='published', + ) + RestrictedCourseRunFactory(course_run=restricted_run, restriction_type='custom-b2c') + + url = f'/api/v1/learner-pathway-program/{self.learner_pathway_program.uuid}/' + if add_restriction_param: + url += '?include_restricted=custom-b2c' + + api_response = self.client.get(url) + data = api_response.json() + assert len(data['courses'][0]['course_runs']) == expected_run_count diff --git a/course_discovery/apps/learner_pathway/api/v1/urls.py b/course_discovery/apps/learner_pathway/api/v1/urls.py index d66de00f1e..07412013d5 100644 --- a/course_discovery/apps/learner_pathway/api/v1/urls.py +++ b/course_discovery/apps/learner_pathway/api/v1/urls.py @@ -4,10 +4,10 @@ from course_discovery.apps.learner_pathway.api.v1 import views router = routers.SimpleRouter() -router.register(r'learner-pathway', views.LearnerPathwayViewSet) -router.register(r'learner-pathway-step', views.LearnerPathwayStepViewSet) -router.register(r'learner-pathway-course', views.LearnerPathwayCourseViewSet) -router.register(r'learner-pathway-program', views.LearnerPathwayProgramViewSet) +router.register(r'learner-pathway', views.LearnerPathwayViewSet, basename='learner-pathway') +router.register(r'learner-pathway-step', views.LearnerPathwayStepViewSet, basename='learner-pathway-step') +router.register(r'learner-pathway-course', views.LearnerPathwayCourseViewSet, basename='learner-pathway-course') +router.register(r'learner-pathway-program', views.LearnerPathwayProgramViewSet, basename='learner-pathway-program') router.register(r'learner-pathway-block', views.LearnerPathwayBlocViewSet) urlpatterns = router.urls diff --git a/course_discovery/apps/learner_pathway/api/v1/views.py b/course_discovery/apps/learner_pathway/api/v1/views.py index 5e8031fb6a..d0be860ddc 100644 --- a/course_discovery/apps/learner_pathway/api/v1/views.py +++ b/course_discovery/apps/learner_pathway/api/v1/views.py @@ -1,7 +1,7 @@ """ API Views for learner_pathway app. """ -from django.db.models import Q +from django.db.models import Prefetch, Q from django_filters.rest_framework import DjangoFilterBackend from rest_framework import status from rest_framework.decorators import action @@ -10,6 +10,9 @@ from rest_framework.viewsets import ReadOnlyModelViewSet from course_discovery.apps.api.pagination import ProxiedPagination +from course_discovery.apps.api.utils import get_excluded_restriction_types +from course_discovery.apps.course_metadata.choices import CourseRunStatus +from course_discovery.apps.course_metadata.models import CourseRun from course_discovery.apps.learner_pathway import models from course_discovery.apps.learner_pathway.api import serializers from course_discovery.apps.learner_pathway.api.filters import PathwayUUIDFilter @@ -24,16 +27,29 @@ class LearnerPathwayViewSet(ReadOnlyModelViewSet): lookup_field = 'uuid' serializer_class = serializers.LearnerPathwaySerializer filter_backends = (DjangoFilterBackend,) - queryset = models.LearnerPathway.objects.prefetch_related('steps').filter(status=PathwayStatus.Active.value) filterset_class = PathwayUUIDFilter # Explicitly support PageNumberPagination and LimitOffsetPagination. Future # versions of this API should only support the system default, PageNumberPagination. pagination_class = ProxiedPagination + def get_queryset(self): + excluded_restriction_types = get_excluded_restriction_types(self.request) + course_runs = CourseRun.objects.filter( + status=CourseRunStatus.Published + ).exclude( + restricted_run__restriction_type__in=excluded_restriction_types + ) + + return models.LearnerPathway.objects.filter(status=PathwayStatus.Active.value).prefetch_related( + 'steps', + Prefetch('steps__learnerpathwaycourse_set__course__course_runs', queryset=course_runs), + Prefetch('steps__learnerpathwayprogram_set__program__courses__course_runs', queryset=course_runs), + ) + @action(detail=True) def snapshot(self, request, uuid): - pathway = get_object_or_404(self.queryset, uuid=uuid, status=PathwayStatus.Active.value) + pathway = get_object_or_404(self.get_queryset(), uuid=uuid, status=PathwayStatus.Active.value) serializer = serializers.LearnerPathwaySerializer(pathway, many=False, context={'request': self.request}) return Response(data=serializer.data, status=status.HTTP_200_OK) @@ -67,12 +83,24 @@ class LearnerPathwayStepViewSet(ReadOnlyModelViewSet): lookup_field = 'uuid' serializer_class = serializers.LearnerPathwayStepSerializer - queryset = models.LearnerPathwayStep.objects.all() # Explicitly support PageNumberPagination and LimitOffsetPagination. Future # versions of this API should only support the system default, PageNumberPagination. pagination_class = ProxiedPagination + def get_queryset(self): + excluded_restriction_types = get_excluded_restriction_types(self.request) + course_runs = CourseRun.objects.filter( + status=CourseRunStatus.Published + ).exclude( + restricted_run__restriction_type__in=excluded_restriction_types + ) + + return models.LearnerPathwayStep.objects.prefetch_related( + Prefetch('learnerpathwaycourse_set__course__course_runs', queryset=course_runs), + Prefetch('learnerpathwayprogram_set__program__courses__course_runs', queryset=course_runs), + ) + class LearnerPathwayCourseViewSet(ReadOnlyModelViewSet): """ @@ -81,12 +109,22 @@ class LearnerPathwayCourseViewSet(ReadOnlyModelViewSet): lookup_field = 'uuid' serializer_class = serializers.LearnerPathwayCourseSerializer - queryset = models.LearnerPathwayCourse.objects.all() # Explicitly support PageNumberPagination and LimitOffsetPagination. Future # versions of this API should only support the system default, PageNumberPagination. pagination_class = ProxiedPagination + def get_queryset(self): + excluded_restriction_types = get_excluded_restriction_types(self.request) + return models.LearnerPathwayCourse.objects.prefetch_related( + Prefetch( + 'course__course_runs', + queryset=CourseRun.objects.filter(status=CourseRunStatus.Published).exclude( + restricted_run__restriction_type__in=excluded_restriction_types + ), + ) + ) + class LearnerPathwayProgramViewSet(ReadOnlyModelViewSet): """ @@ -95,12 +133,22 @@ class LearnerPathwayProgramViewSet(ReadOnlyModelViewSet): lookup_field = 'uuid' serializer_class = serializers.LearnerPathwayProgramSerializer - queryset = models.LearnerPathwayProgram.objects.all() # Explicitly support PageNumberPagination and LimitOffsetPagination. Future # versions of this API should only support the system default, PageNumberPagination. pagination_class = ProxiedPagination + def get_queryset(self): + excluded_restriction_types = get_excluded_restriction_types(self.request) + return models.LearnerPathwayProgram.objects.prefetch_related( + Prefetch( + 'program__courses__course_runs', + queryset=CourseRun.objects.filter(status=CourseRunStatus.Published).exclude( + restricted_run__restriction_type__in=excluded_restriction_types + ), + ) + ) + class LearnerPathwayBlocViewSet(ReadOnlyModelViewSet): """ diff --git a/course_discovery/apps/learner_pathway/models.py b/course_discovery/apps/learner_pathway/models.py index 7278f612a3..0cca9fdc4f 100644 --- a/course_discovery/apps/learner_pathway/models.py +++ b/course_discovery/apps/learner_pathway/models.py @@ -15,7 +15,6 @@ from taxonomy.utils import get_whitelisted_serialized_skills from course_discovery.apps.core.models import Partner -from course_discovery.apps.course_metadata.choices import CourseRunStatus from course_discovery.apps.course_metadata.models import Course, Program from course_discovery.apps.course_metadata.utils import UploadToFieldNamePath from course_discovery.apps.learner_pathway import constants @@ -299,23 +298,15 @@ def get_skills(self) -> [str]: return program_skills - def get_linked_courses_and_course_runs(self, excluded_restriction_types=None) -> [dict]: + def get_linked_courses_and_course_runs(self): """ Returns list of dict where each dict contains a course key linked with program and all its course runs """ - if excluded_restriction_types is None: - excluded_restriction_types = [] courses = [] for course in self.program.courses.all(): - course_runs = list( - course.course_runs.filter( - status=CourseRunStatus.Published - ).exclude( - restricted_run__restriction_type__in=excluded_restriction_types - ).values('key') - ) - courses.append({"key": course.key, "course_runs": course_runs}) + course_runs = [{'key': course_run.key} for course_run in course.course_runs.all()] + courses.append({'key': course.key, 'course_runs': course_runs}) return courses def __str__(self):