Commit bbdeaa37 by Matthew Piatetsky Committed by GitHub

Merge pull request #503 from edx/hide_unpublished_course_runs

Add flag to hide unpublished course runs on the course endpoint
parents 80246abd fce457f6
...@@ -472,8 +472,24 @@ class CourseSerializer(MinimalCourseSerializer): ...@@ -472,8 +472,24 @@ class CourseSerializer(MinimalCourseSerializer):
class CourseWithProgramsSerializer(CourseSerializer): class CourseWithProgramsSerializer(CourseSerializer):
"""A ``CourseSerializer`` which includes programs.""" """A ``CourseSerializer`` which includes programs."""
course_runs = serializers.SerializerMethodField()
programs = serializers.SerializerMethodField() programs = serializers.SerializerMethodField()
def get_course_runs(self, course):
course_runs = course.course_runs.exclude(hidden=True)
if self.context.get('published_course_runs_only'):
course_runs = course_runs.filter(status=CourseRunStatus.Published)
return CourseRunSerializer(
course_runs,
many=True,
context={
'request': self.context.get('request'),
'exclude_utm': self.context.get('exclude_utm'),
}
).data
def get_programs(self, obj): def get_programs(self, obj):
if self.context.get('include_deleted_programs'): if self.context.get('include_deleted_programs'):
eligible_programs = obj.programs.all() eligible_programs = obj.programs.all()
......
...@@ -155,6 +155,7 @@ class CourseSerializerTests(MinimalCourseSerializerTests): ...@@ -155,6 +155,7 @@ class CourseSerializerTests(MinimalCourseSerializerTests):
self.assertEqual(serializer.data['marketing_url'], course.marketing_url) self.assertEqual(serializer.data['marketing_url'], course.marketing_url)
@ddt.ddt
class CourseWithProgramsSerializerTests(CourseSerializerTests): class CourseWithProgramsSerializerTests(CourseSerializerTests):
serializer_class = CourseWithProgramsSerializer serializer_class = CourseWithProgramsSerializer
...@@ -198,6 +199,21 @@ class CourseWithProgramsSerializerTests(CourseSerializerTests): ...@@ -198,6 +199,21 @@ class CourseWithProgramsSerializerTests(CourseSerializerTests):
) )
self.assertEqual(serializer.data, self.get_expected_data(self.course, self.request)) self.assertEqual(serializer.data, self.get_expected_data(self.course, self.request))
@ddt.data(0, 1)
def test_published_course_runs_only(self, published_course_runs_only):
"""
Test that the published_course_runs_only flag hides unpublished course runs
"""
unpublished_course_run = CourseRunFactory(status=CourseRunStatus.Unpublished)
published_course_run = CourseRunFactory(status=CourseRunStatus.Published)
self.course.course_runs.add(unpublished_course_run, published_course_run)
self.request = make_request()
serializer = self.serializer_class(
self.course,
context={'request': self.request, 'published_course_runs_only': published_course_runs_only}
)
self.assertEqual(len(serializer.data['course_runs']), 2 - published_course_runs_only)
class MinimalCourseRunSerializerTests(TestCase): class MinimalCourseRunSerializerTests(TestCase):
serializer_class = MinimalCourseRunSerializer serializer_class = MinimalCourseRunSerializer
......
import ddt
from django.db.models.functions import Lower from django.db.models.functions import Lower
from rest_framework.reverse import reverse from rest_framework.reverse import reverse
from rest_framework.test import APITestCase from rest_framework.test import APITestCase
from course_discovery.apps.api.v1.tests.test_views.mixins import SerializationMixin from course_discovery.apps.api.v1.tests.test_views.mixins import SerializationMixin
from course_discovery.apps.core.tests.factories import UserFactory, USER_PASSWORD from course_discovery.apps.core.tests.factories import UserFactory, USER_PASSWORD
from course_discovery.apps.course_metadata.choices import ProgramStatus from course_discovery.apps.course_metadata.choices import ProgramStatus, CourseRunStatus
from course_discovery.apps.course_metadata.models import Course from course_discovery.apps.course_metadata.models import Course
from course_discovery.apps.course_metadata.tests.factories import CourseFactory, ProgramFactory from course_discovery.apps.course_metadata.tests.factories import CourseFactory, CourseRunFactory, ProgramFactory
@ddt.ddt
class CourseViewSetTests(SerializationMixin, APITestCase): class CourseViewSetTests(SerializationMixin, APITestCase):
maxDiff = None maxDiff = None
...@@ -22,7 +24,7 @@ class CourseViewSetTests(SerializationMixin, APITestCase): ...@@ -22,7 +24,7 @@ class CourseViewSetTests(SerializationMixin, APITestCase):
""" Verify the endpoint returns the details for a single course. """ """ Verify the endpoint returns the details for a single course. """
url = reverse('api:v1:course-detail', kwargs={'key': self.course.key}) url = reverse('api:v1:course-detail', kwargs={'key': self.course.key})
with self.assertNumQueries(19): with self.assertNumQueries(20):
response = self.client.get(url) response = self.client.get(url)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(response.data, self.serialize_course(self.course)) self.assertEqual(response.data, self.serialize_course(self.course))
...@@ -31,7 +33,7 @@ class CourseViewSetTests(SerializationMixin, APITestCase): ...@@ -31,7 +33,7 @@ class CourseViewSetTests(SerializationMixin, APITestCase):
""" Verify the endpoint returns no deleted associated programs """ """ Verify the endpoint returns no deleted associated programs """
ProgramFactory(courses=[self.course], status=ProgramStatus.Deleted) ProgramFactory(courses=[self.course], status=ProgramStatus.Deleted)
url = reverse('api:v1:course-detail', kwargs={'key': self.course.key}) url = reverse('api:v1:course-detail', kwargs={'key': self.course.key})
with self.assertNumQueries(12): with self.assertNumQueries(13):
response = self.client.get(url) response = self.client.get(url)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(response.data.get('programs'), []) self.assertEqual(response.data.get('programs'), [])
...@@ -44,7 +46,7 @@ class CourseViewSetTests(SerializationMixin, APITestCase): ...@@ -44,7 +46,7 @@ class CourseViewSetTests(SerializationMixin, APITestCase):
ProgramFactory(courses=[self.course], status=ProgramStatus.Deleted) ProgramFactory(courses=[self.course], status=ProgramStatus.Deleted)
url = reverse('api:v1:course-detail', kwargs={'key': self.course.key}) url = reverse('api:v1:course-detail', kwargs={'key': self.course.key})
url += '?include_deleted_programs=1' url += '?include_deleted_programs=1'
with self.assertNumQueries(22): with self.assertNumQueries(23):
response = self.client.get(url) response = self.client.get(url)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual( self.assertEqual(
...@@ -52,11 +54,29 @@ class CourseViewSetTests(SerializationMixin, APITestCase): ...@@ -52,11 +54,29 @@ class CourseViewSetTests(SerializationMixin, APITestCase):
self.serialize_course(self.course, extra_context={'include_deleted_programs': True}) self.serialize_course(self.course, extra_context={'include_deleted_programs': True})
) )
@ddt.data(1, 0)
def test_get_include_published_course_run(self, published_course_runs_only):
"""
Verify the endpoint returns hides unpublished programs if
the 'published_course_runs_only' flag is set to True
"""
published_course_run = CourseRunFactory(status=CourseRunStatus.Published)
unpublished_course_run = CourseRunFactory(status=CourseRunStatus.Unpublished)
self.course.course_runs.add(published_course_run, unpublished_course_run)
url = reverse('api:v1:course-detail', kwargs={'key': self.course.key})
url += '?published_course_runs_only={}'.format(published_course_runs_only)
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
self.assertEqual(
response.data,
self.serialize_course(self.course, extra_context={'published_course_runs_only': published_course_runs_only})
)
def test_list(self): def test_list(self):
""" Verify the endpoint returns a list of all courses. """ """ Verify the endpoint returns a list of all courses. """
url = reverse('api:v1:course-list') url = reverse('api:v1:course-list')
with self.assertNumQueries(25): with self.assertNumQueries(26):
response = self.client.get(url) response = self.client.get(url)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertListEqual( self.assertListEqual(
...@@ -83,7 +103,7 @@ class CourseViewSetTests(SerializationMixin, APITestCase): ...@@ -83,7 +103,7 @@ class CourseViewSetTests(SerializationMixin, APITestCase):
keys = ','.join([course.key for course in courses]) keys = ','.join([course.key for course in courses])
url = '{root}?keys={keys}'.format(root=reverse('api:v1:course-list'), keys=keys) url = '{root}?keys={keys}'.format(root=reverse('api:v1:course-list'), keys=keys)
with self.assertNumQueries(38): with self.assertNumQueries(41):
response = self.client.get(url) response = self.client.get(url)
self.assertListEqual(response.data['results'], self.serialize_course(courses, many=True)) self.assertListEqual(response.data['results'], self.serialize_course(courses, many=True))
......
...@@ -46,6 +46,7 @@ class CourseViewSet(viewsets.ReadOnlyModelViewSet): ...@@ -46,6 +46,7 @@ class CourseViewSet(viewsets.ReadOnlyModelViewSet):
context.update({ context.update({
'exclude_utm': get_query_param(self.request, 'exclude_utm'), 'exclude_utm': get_query_param(self.request, 'exclude_utm'),
'include_deleted_programs': get_query_param(self.request, 'include_deleted_programs'), 'include_deleted_programs': get_query_param(self.request, 'include_deleted_programs'),
'published_course_runs_only': get_query_param(self.request, 'published_course_runs_only'),
}) })
return context return context
...@@ -66,6 +67,12 @@ class CourseViewSet(viewsets.ReadOnlyModelViewSet): ...@@ -66,6 +67,12 @@ class CourseViewSet(viewsets.ReadOnlyModelViewSet):
type: string type: string
paramType: query paramType: query
multiple: false multiple: false
- name: published_course_runs_only
description: Filter course runs by published ones only
required: false
type: integer
paramType: query
mulitple: false
- name: exclude_utm - name: exclude_utm
description: Exclude UTM parameters from marketing URLs. description: Exclude UTM parameters from marketing URLs.
required: false required: false
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment