From 9bbf875fbb8d7f6905af99fec74355815585232e Mon Sep 17 00:00:00 2001 From: Matt Bertrand Date: Wed, 1 Oct 2025 20:59:09 -0400 Subject: [PATCH 1/2] Only show published runs of courses in /items/ endpoint --- learning_resources/views.py | 26 ++++++++++++++++++-- learning_resources/views_test.py | 41 ++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 2 deletions(-) diff --git a/learning_resources/views.py b/learning_resources/views.py index 873b4b9504..a6a6597c04 100644 --- a/learning_resources/views.py +++ b/learning_resources/views.py @@ -6,7 +6,7 @@ import rapidjson from django.conf import settings from django.db import transaction -from django.db.models import Count, F, Q, QuerySet +from django.db.models import Count, F, Prefetch, Q, QuerySet from django.http import Http404, HttpResponse from django.utils.decorators import method_decorator from django.views.decorators.cache import cache_page @@ -545,7 +545,29 @@ class ResourceListItemsViewSet(NestedViewSetMixin, viewsets.ReadOnlyModelViewSet queryset = ( LearningResourceRelationship.objects.select_related("child") .prefetch_related( - "child__runs", "child__runs__instructors", "child__runs__resource_prices" + Prefetch( + "child__topics", + queryset=LearningResourceTopic.objects.for_serialization(), + ), + Prefetch( + "child__offered_by", + queryset=LearningResourceOfferor.objects.for_serialization(), + ), + Prefetch( + "child__departments", + queryset=LearningResourceDepartment.objects.for_serialization( + prefetch_school=True + ).select_related("school"), + ), + Prefetch( + "child__runs", + queryset=LearningResourceRun.objects.filter( + published=True + ).for_serialization(), + ), + "child__runs__instructors", + "child__runs__resource_prices", + "child__topics", ) .filter(child__published=True) ) diff --git a/learning_resources/views_test.py b/learning_resources/views_test.py index eede0967a6..a599850ce6 100644 --- a/learning_resources/views_test.py +++ b/learning_resources/views_test.py @@ -1521,3 +1521,44 @@ def test_course_run_problems_endpoint(client, user_role, django_user_model): "detail": "Authentication credentials were not provided.", "error_type": "NotAuthenticated", } + + +def test_resource_list_items_only_shows_published_runs(client, user): + """Test that ResourceListItemsViewSet only returns published runs for child resources""" + + program = LearningResourceFactory.create( + resource_type=LearningResourceType.program.name, + published=True, + ) + course = LearningResourceFactory.create( + resource_type=LearningResourceType.course.name, + published=True, + ) + published_run = LearningResourceRunFactory.create( + learning_resource=course, + published=True, + ) + unpublished_run = LearningResourceRunFactory.create( + learning_resource=course, + published=False, + ) + LearningResourceRelationship.objects.create( + parent=program, + child=course, + relation_type=LearningResourceRelationTypes.PROGRAM_COURSES.value, + ) + + client.force_login(user) + url = reverse( + "lr:v1:learningresources_items-list", + kwargs={"learning_resource_id": program.id}, + ) + resp = client.get(url) + + results = resp.json()["results"] + child_data = results[0]["child"] + assert child_data["id"] == course.id + run_ids = [run["id"] for run in child_data["runs"]] + assert published_run.id in run_ids + assert unpublished_run.id not in run_ids + assert len(child_data["runs"]) == 1 From cb57fcaba17a113c5408d22e4b9ef3fca67a6f63 Mon Sep 17 00:00:00 2001 From: Matt Bertrand Date: Wed, 1 Oct 2025 21:53:02 -0400 Subject: [PATCH 2/2] Fix test --- learning_resources/views_test.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/learning_resources/views_test.py b/learning_resources/views_test.py index a599850ce6..87ef7560d2 100644 --- a/learning_resources/views_test.py +++ b/learning_resources/views_test.py @@ -1523,7 +1523,7 @@ def test_course_run_problems_endpoint(client, user_role, django_user_model): } -def test_resource_list_items_only_shows_published_runs(client, user): +def test_resource_items_only_shows_published_runs(client, user): """Test that ResourceListItemsViewSet only returns published runs for child resources""" program = LearningResourceFactory.create( @@ -1533,30 +1533,38 @@ def test_resource_list_items_only_shows_published_runs(client, user): course = LearningResourceFactory.create( resource_type=LearningResourceType.course.name, published=True, + create_runs=False, ) + program.resources.set( + [course], through_defaults={"relation_type": "program_courses"} + ) + published_run = LearningResourceRunFactory.create( - learning_resource=course, published=True, + learning_resource=course, ) unpublished_run = LearningResourceRunFactory.create( learning_resource=course, published=False, ) - LearningResourceRelationship.objects.create( - parent=program, - child=course, - relation_type=LearningResourceRelationTypes.PROGRAM_COURSES.value, + course.runs.set( + [ + published_run, + unpublished_run, + ] ) + assert course.runs.count() == 2 client.force_login(user) url = reverse( - "lr:v1:learningresources_items-list", + "lr:v1:learning_resource_items_api-list", kwargs={"learning_resource_id": program.id}, ) resp = client.get(url) results = resp.json()["results"] - child_data = results[0]["child"] + assert len(results) == 1 + child_data = results[0]["resource"] assert child_data["id"] == course.id run_ids = [run["id"] for run in child_data["runs"]] assert published_run.id in run_ids