Commit 54feb857 by Clinton Blackburn Committed by GitHub

Merge pull request #345 from edx/clintonb/programs-fix

Programs endpoint fixes
parents 83a32240 fbd4e1c5
...@@ -238,7 +238,7 @@ class OrganizationSerializer(TaggitSerializer, MinimalOrganizationSerializer): ...@@ -238,7 +238,7 @@ class OrganizationSerializer(TaggitSerializer, MinimalOrganizationSerializer):
class Meta(MinimalOrganizationSerializer.Meta): class Meta(MinimalOrganizationSerializer.Meta):
model = Organization model = Organization
fields = MinimalOrganizationSerializer.Meta.fields + ( fields = MinimalOrganizationSerializer.Meta.fields + (
'description', 'homepage_url', 'tags', 'logo_image_url', 'marketing_url' 'description', 'homepage_url', 'tags', 'logo_image_url', 'marketing_url',
) )
...@@ -333,6 +333,7 @@ class ContainedCourseRunsSerializer(serializers.Serializer): ...@@ -333,6 +333,7 @@ class ContainedCourseRunsSerializer(serializers.Serializer):
class MinimalCourseSerializer(TimestampModelSerializer): class MinimalCourseSerializer(TimestampModelSerializer):
course_runs = MinimalCourseRunSerializer(many=True)
owners = MinimalOrganizationSerializer(many=True, source='authoring_organizations') owners = MinimalOrganizationSerializer(many=True, source='authoring_organizations')
class Meta: class Meta:
......
from django.test import TestCase from django.test import TestCase
from course_discovery.apps.api.fields import ImageField from course_discovery.apps.api.fields import ImageField, StdImageSerializerField
from course_discovery.apps.api.tests.test_serializers import make_request
from course_discovery.apps.core.tests.helpers import make_image_file
from course_discovery.apps.course_metadata.tests.factories import ProgramFactory
class ImageFieldTests(TestCase): class ImageFieldTests(TestCase):
...@@ -13,3 +16,27 @@ class ImageFieldTests(TestCase): ...@@ -13,3 +16,27 @@ class ImageFieldTests(TestCase):
'width': None 'width': None
} }
self.assertEqual(ImageField().to_representation(value), expected) self.assertEqual(ImageField().to_representation(value), expected)
# pylint: disable=no-member
class StdImageSerializerFieldTests(TestCase):
def test_to_representation(self):
request = make_request()
# TODO Create test-only model to avoid unnecessary dependency on Program model.
program = ProgramFactory(banner_image=make_image_file('test.jpg'))
field = StdImageSerializerField()
field._context = {'request': request} # pylint: disable=protected-access
expected = {
size_key: {
'url': '{}{}'.format(
'http://testserver',
getattr(program.banner_image, size_key).url
),
'width': program.banner_image.field.variations[size_key]['width'],
'height': program.banner_image.field.variations[size_key]['height']
}
for size_key in program.banner_image.field.variations
}
self.assertDictEqual(field.to_representation(program.banner_image), expected)
import unittest
from datetime import datetime from datetime import datetime
from urllib.parse import urlencode from urllib.parse import urlencode
...@@ -8,15 +7,16 @@ from haystack.query import SearchQuerySet ...@@ -8,15 +7,16 @@ from haystack.query import SearchQuerySet
from opaque_keys.edx.keys import CourseKey from opaque_keys.edx.keys import CourseKey
from rest_framework.test import APIRequestFactory from rest_framework.test import APIRequestFactory
from course_discovery.apps.api.fields import ImageField from course_discovery.apps.api.fields import ImageField, StdImageSerializerField
from course_discovery.apps.api.serializers import ( from course_discovery.apps.api.serializers import (
CatalogSerializer, CourseSerializer, CourseRunSerializer, ContainedCoursesSerializer, ImageSerializer, CatalogSerializer, CourseRunSerializer, ContainedCoursesSerializer, ImageSerializer,
SubjectSerializer, PrerequisiteSerializer, VideoSerializer, OrganizationSerializer, SeatSerializer, SubjectSerializer, PrerequisiteSerializer, VideoSerializer, OrganizationSerializer, SeatSerializer,
PersonSerializer, AffiliateWindowSerializer, ContainedCourseRunsSerializer, CourseRunSearchSerializer, PersonSerializer, AffiliateWindowSerializer, ContainedCourseRunsSerializer, CourseRunSearchSerializer,
ProgramSerializer, ProgramSearchSerializer, ProgramCourseSerializer, NestedProgramSerializer, ProgramSerializer, ProgramSearchSerializer, ProgramCourseSerializer, NestedProgramSerializer,
CourseRunWithProgramsSerializer, CourseWithProgramsSerializer, CorporateEndorsementSerializer, CourseRunWithProgramsSerializer, CourseWithProgramsSerializer, CorporateEndorsementSerializer,
FAQSerializer, EndorsementSerializer, PositionSerializer, FlattenedCourseRunWithCourseSerializer, FAQSerializer, EndorsementSerializer, PositionSerializer, FlattenedCourseRunWithCourseSerializer,
MinimalCourseSerializer, MinimalOrganizationSerializer, MinimalCourseRunSerializer MinimalCourseSerializer, MinimalOrganizationSerializer, MinimalCourseRunSerializer, MinimalProgramSerializer,
CourseSerializer
) )
from course_discovery.apps.catalogs.tests.factories import CatalogFactory from course_discovery.apps.catalogs.tests.factories import CatalogFactory
from course_discovery.apps.core.models import User from course_discovery.apps.core.models import User
...@@ -32,7 +32,7 @@ from course_discovery.apps.course_metadata.tests.factories import ( ...@@ -32,7 +32,7 @@ from course_discovery.apps.course_metadata.tests.factories import (
from course_discovery.apps.ietf_language_tags.models import LanguageTag from course_discovery.apps.ietf_language_tags.models import LanguageTag
# pylint:disable=no-member # pylint:disable=no-member, test-inherits-tests
def json_date_format(datetime_obj): def json_date_format(datetime_obj):
return datetime.strftime(datetime_obj, "%Y-%m-%dT%H:%M:%S.%fZ") return datetime.strftime(datetime_obj, "%Y-%m-%dT%H:%M:%S.%fZ")
...@@ -94,20 +94,36 @@ class CatalogSerializerTests(TestCase): ...@@ -94,20 +94,36 @@ class CatalogSerializerTests(TestCase):
self.assertEqual(User.objects.filter(username=username).count(), 0) # pylint: disable=no-member self.assertEqual(User.objects.filter(username=username).count(), 0) # pylint: disable=no-member
class CourseSerializerTests(TestCase): class MinimalCourseSerializerTests(TestCase):
def test_data(self): serializer_class = MinimalCourseSerializer
course = CourseFactory()
video = course.video
request = make_request() def get_expected_data(self, course, request):
context = {'request': request}
CourseRunFactory.create_batch(3, course=course) return {
serializer = CourseWithProgramsSerializer(course, context={'request': request})
expected = {
'uuid': str(course.uuid),
'key': course.key, 'key': course.key,
'uuid': str(course.uuid),
'title': course.title, 'title': course.title,
'course_runs': MinimalCourseRunSerializer(course.course_runs, many=True, context=context).data,
'owners': MinimalOrganizationSerializer(course.authoring_organizations, many=True, context=context).data,
}
def test_data(self):
request = make_request()
organizations = OrganizationFactory()
course = CourseFactory(authoring_organizations=[organizations])
CourseRunFactory.create_batch(2, course=course)
serializer = self.serializer_class(course, context={'request': request})
expected = self.get_expected_data(course, request)
self.assertDictEqual(serializer.data, expected)
class CourseSerializerTests(MinimalCourseSerializerTests):
serializer_class = CourseSerializer
def get_expected_data(self, course, request):
expected = super().get_expected_data(course, request)
expected.update({
'short_description': course.short_description, 'short_description': course.short_description,
'full_description': course.full_description, 'full_description': course.full_description,
'level_type': course.level_type.name, 'level_type': course.level_type.name,
...@@ -115,11 +131,9 @@ class CourseSerializerTests(TestCase): ...@@ -115,11 +131,9 @@ class CourseSerializerTests(TestCase):
'prerequisites': [], 'prerequisites': [],
'expected_learning_items': [], 'expected_learning_items': [],
'image': ImageField().to_representation(course.card_image_url), 'image': ImageField().to_representation(course.card_image_url),
'video': VideoSerializer(video).data, 'video': VideoSerializer(course.video).data,
'owners': OrganizationSerializer(course.authoring_organizations, many=True).data,
'sponsors': OrganizationSerializer(course.sponsoring_organizations, many=True).data, 'sponsors': OrganizationSerializer(course.sponsoring_organizations, many=True).data,
'modified': json_date_format(course.modified), # pylint: disable=no-member 'modified': json_date_format(course.modified), # pylint: disable=no-member
'course_runs': CourseRunSerializer(course.course_runs, many=True, context={'request': request}).data,
'marketing_url': '{url}?{params}'.format( 'marketing_url': '{url}?{params}'.format(
url=course.marketing_url, url=course.marketing_url,
params=urlencode({ params=urlencode({
...@@ -127,23 +141,47 @@ class CourseSerializerTests(TestCase): ...@@ -127,23 +141,47 @@ class CourseSerializerTests(TestCase):
'utm_medium': request.user.referral_tracking_id, 'utm_medium': request.user.referral_tracking_id,
}) })
), ),
'course_runs': CourseRunSerializer(course.course_runs, many=True, context={'request': request}).data,
'owners': OrganizationSerializer(course.authoring_organizations, many=True).data,
})
return expected
class CourseWithProgramsSerializerTests(CourseSerializerTests): # pylint: disable=test-inherits-tests
serializer_class = CourseWithProgramsSerializer
def get_expected_data(self, course, request):
expected = super().get_expected_data(course, request)
expected.update({
'programs': NestedProgramSerializer(course.programs, many=True, context={'request': request}).data, 'programs': NestedProgramSerializer(course.programs, many=True, context={'request': request}).data,
} })
return expected
self.assertDictEqual(serializer.data, expected)
class MinimalCourseRunSerializerTests(TestCase):
serializer_class = MinimalCourseRunSerializer
def get_expected_data(self, course_run, request): # pylint: disable=unused-argument
return {
'key': course_run.key,
'uuid': str(course_run.uuid),
'title': course_run.title,
}
class CourseRunSerializerTests(TestCase):
def test_data(self): def test_data(self):
request = make_request() request = make_request()
course_run = CourseRunFactory() course_run = CourseRunFactory()
course = course_run.course serializer = self.serializer_class(course_run, context={'request': request})
video = course_run.video expected = self.get_expected_data(course_run, request)
serializer = CourseRunSerializer(course_run, context={'request': request}) self.assertDictEqual(serializer.data, expected)
ProgramFactory(courses=[course])
expected = {
'uuid': str(course_run.uuid), class CourseRunSerializerTests(MinimalCourseRunSerializerTests): # pylint: disable=test-inherits-tests
serializer_class = CourseRunSerializer
def get_expected_data(self, course_run, request):
expected = super().get_expected_data(course_run, request)
expected.update({
'course': course_run.course.key, 'course': course_run.course.key,
'key': course_run.key, 'key': course_run.key,
'title': course_run.title, # pylint: disable=no-member 'title': course_run.title, # pylint: disable=no-member
...@@ -155,7 +193,7 @@ class CourseRunSerializerTests(TestCase): ...@@ -155,7 +193,7 @@ class CourseRunSerializerTests(TestCase):
'enrollment_end': json_date_format(course_run.enrollment_end), 'enrollment_end': json_date_format(course_run.enrollment_end),
'announcement': json_date_format(course_run.announcement), 'announcement': json_date_format(course_run.announcement),
'image': ImageField().to_representation(course_run.card_image_url), 'image': ImageField().to_representation(course_run.card_image_url),
'video': VideoSerializer(video).data, 'video': VideoSerializer(course_run.video).data,
'pacing_type': course_run.pacing_type, 'pacing_type': course_run.pacing_type,
'content_language': course_run.language.code, 'content_language': course_run.language.code,
'transcript_languages': [], 'transcript_languages': [],
...@@ -174,9 +212,8 @@ class CourseRunSerializerTests(TestCase): ...@@ -174,9 +212,8 @@ class CourseRunSerializerTests(TestCase):
), ),
'level_type': course_run.level_type.name, 'level_type': course_run.level_type.name,
'availability': course_run.availability, 'availability': course_run.availability,
} })
return expected
self.assertDictEqual(serializer.data, expected)
class CourseRunWithProgramsSerializerTests(TestCase): class CourseRunWithProgramsSerializerTests(TestCase):
...@@ -195,7 +232,6 @@ class CourseRunWithProgramsSerializerTests(TestCase): ...@@ -195,7 +232,6 @@ class CourseRunWithProgramsSerializerTests(TestCase):
self.assertDictEqual(serializer.data, expected) self.assertDictEqual(serializer.data, expected)
@unittest.skip('This test is disabled until we can determine why assertDictEqual() fails for two equivalent inputs.')
class FlattenedCourseRunWithCourseSerializerTests(TestCase): # pragma: no cover class FlattenedCourseRunWithCourseSerializerTests(TestCase): # pragma: no cover
def serialize_seats(self, course_run): def serialize_seats(self, course_run):
seats = { seats = {
...@@ -245,7 +281,7 @@ class FlattenedCourseRunWithCourseSerializerTests(TestCase): # pragma: no cover ...@@ -245,7 +281,7 @@ class FlattenedCourseRunWithCourseSerializerTests(TestCase): # pragma: no cover
def get_expected_data(self, request, course_run): def get_expected_data(self, request, course_run):
course = course_run.course course = course_run.course
serializer_context = {'request': request} serializer_context = {'request': request}
expected = CourseRunSerializer(course_run, context=serializer_context).data expected = dict(CourseRunSerializer(course_run, context=serializer_context).data)
expected.update({ expected.update({
'subjects': self.serialize_items(course.subjects.all(), 'name'), 'subjects': self.serialize_items(course.subjects.all(), 'name'),
'seats': self.serialize_seats(course_run), 'seats': self.serialize_seats(course_run),
...@@ -286,10 +322,7 @@ class FlattenedCourseRunWithCourseSerializerTests(TestCase): # pragma: no cover ...@@ -286,10 +322,7 @@ class FlattenedCourseRunWithCourseSerializerTests(TestCase): # pragma: no cover
self.assertDictEqual(serializer.data, expected) self.assertDictEqual(serializer.data, expected)
@ddt.ddt
class ProgramCourseSerializerTests(TestCase): class ProgramCourseSerializerTests(TestCase):
maxDiff = None
def setUp(self): def setUp(self):
super(ProgramCourseSerializerTests, self).setUp() super(ProgramCourseSerializerTests, self).setUp()
self.request = make_request() self.request = make_request()
...@@ -310,7 +343,6 @@ class ProgramCourseSerializerTests(TestCase): ...@@ -310,7 +343,6 @@ class ProgramCourseSerializerTests(TestCase):
self.assertSequenceEqual(serializer.data, expected) self.assertSequenceEqual(serializer.data, expected)
@unittest.skip('@clintonb to fix later')
def test_with_runs(self): def test_with_runs(self):
for course in self.course_list: for course in self.course_list:
CourseRunFactory.create_batch(2, course=course) CourseRunFactory.create_batch(2, course=course)
...@@ -342,104 +374,97 @@ class ProgramCourseSerializerTests(TestCase): ...@@ -342,104 +374,97 @@ class ProgramCourseSerializerTests(TestCase):
context={'request': self.request}).data context={'request': self.request}).data
self.assertDictEqual(serializer.data, expected) self.assertDictEqual(serializer.data, expected)
@unittest.skip('@clintonb to fix later') def test_with_published_course_runs_only_context(self):
@ddt.data( """ Verify setting the published_course_runs_only context value excludes unpublished course runs. """
[CourseRunStatus.Unpublished, 1], # Create a program and course. The course should have both published and un-published course runs.
[CourseRunStatus.Unpublished, 0], course = CourseFactory()
[CourseRunStatus.Published, 1], courses = [course]
[CourseRunStatus.Published, 0] program = ProgramFactory(courses=courses)
) unpublished_course_run = CourseRunFactory(status=CourseRunStatus.Unpublished, course=course)
@ddt.unpack CourseRunFactory(status=CourseRunStatus.Published, course=course)
def test_with_published_only_querystring(self, course_run_status, published_course_runs_only):
""" # We do NOT expect the results to included the unpublished data
Test the serializer's ability to filter out course_runs based on expected = MinimalCourseSerializer(courses, many=True, context={'request': self.request}).data
"published_course_runs_only" query string expected[0]['course_runs'] = [course_run for course_run in expected[0]['course_runs'] if
""" course_run['uuid'] != str(unpublished_course_run.uuid)]
expected = CourseSerializer(self.course_list, many=True, context={'request': self.request}).data self.assertEqual(len(expected[0]['course_runs']), 1)
for course in self.course_list:
CourseRunFactory.create_batch(2, status=course_run_status, course=course)
serializer = ProgramCourseSerializer( serializer = ProgramCourseSerializer(
self.course_list, courses,
many=True, many=True,
context={ context={
'request': self.request, 'request': self.request,
'program': self.program, 'program': program,
'published_course_runs_only': published_course_runs_only, 'published_course_runs_only': True,
} }
) )
validate_data = serializer.data
if not published_course_runs_only or course_run_status != CourseRunStatus.Unpublished: self.assertSequenceEqual(serializer.data, expected)
expected = MinimalCourseSerializer(self.course_list, many=True, context={'request': self.request}).data
self.assertSequenceEqual(validate_data, expected)
class MinimalProgramSerializerTests(TestCase):
serializer_class = MinimalProgramSerializer
class ProgramSerializerTests(TestCase): def create_program(self):
maxDiff = None organizations = OrganizationFactory.create_batch(2)
person = PersonFactory()
courses = CourseFactory.create_batch(3)
for course in courses:
CourseRunFactory.create_batch(2, course=course, staff=[person])
@unittest.skip('@clintonb to fix later')
def test_data(self):
request = make_request()
org_list = OrganizationFactory.create_batch(1)
course_list = CourseFactory.create_batch(3)
for course in course_list:
CourseRunFactory.create_batch(
3,
course=course,
enrollment_start=datetime(2014, 1, 1),
start=datetime(2014, 1, 1)
)
corporate_endorsements = CorporateEndorsementFactory.create_batch(1)
individual_endorsements = EndorsementFactory.create_batch(1)
staff = PersonFactory.create_batch(1)
job_outlook_items = JobOutlookItemFactory.create_batch(1)
expected_learning_items = ExpectedLearningItemFactory.create_batch(1)
program = ProgramFactory( program = ProgramFactory(
authoring_organizations=org_list, courses=courses,
courses=course_list, authoring_organizations=organizations,
credit_backing_organizations=org_list, credit_backing_organizations=organizations,
corporate_endorsements=corporate_endorsements, corporate_endorsements=CorporateEndorsementFactory.create_batch(1),
individual_endorsements=individual_endorsements, individual_endorsements=EndorsementFactory.create_batch(1),
expected_learning_items=expected_learning_items, expected_learning_items=ExpectedLearningItemFactory.create_batch(1),
staff=staff, job_outlook_items=JobOutlookItemFactory.create_batch(1),
job_outlook_items=job_outlook_items, banner_image=make_image_file('test_banner.jpg'),
video=VideoFactory()
) )
program.banner_image = make_image_file('test_banner.jpg') return program
program.save()
serializer = ProgramSerializer(program, context={'request': request})
expected_banner_image_urls = {
size_key: {
'url': '{}{}'.format(
'http://testserver',
getattr(program.banner_image, size_key).url
),
'width': program.banner_image.field.variations[size_key]['width'],
'height': program.banner_image.field.variations[size_key]['height']
}
for size_key in program.banner_image.field.variations
}
expected = { def get_expected_data(self, program, request):
image_field = StdImageSerializerField()
image_field._context = {'request': request} # pylint: disable=protected-access
return {
'uuid': str(program.uuid), 'uuid': str(program.uuid),
'title': program.title, 'title': program.title,
'subtitle': program.subtitle, 'subtitle': program.subtitle,
'type': program.type.name, 'type': program.type.name,
'status': program.status,
'marketing_slug': program.marketing_slug, 'marketing_slug': program.marketing_slug,
'marketing_url': program.marketing_url, 'marketing_url': program.marketing_url,
'card_image_url': program.card_image_url, 'banner_image': image_field.to_representation(program.banner_image),
'video': None, 'courses': ProgramCourseSerializer(program.courses, many=True,
'banner_image': expected_banner_image_urls, context={'request': request, 'program': program}).data,
'authoring_organizations': MinimalOrganizationSerializer(program.authoring_organizations, many=True).data, 'authoring_organizations': MinimalOrganizationSerializer(program.authoring_organizations, many=True).data,
'card_image_url': program.card_image_url,
}
def test_data(self):
request = make_request()
program = self.create_program()
serializer = self.serializer_class(program, context={'request': request})
expected = self.get_expected_data(program, request)
self.assertDictEqual(serializer.data, expected)
class ProgramSerializerTests(MinimalProgramSerializerTests): # pylint: disable=test-inherits-tests
serializer_class = ProgramSerializer
def get_expected_data(self, program, request):
expected = super().get_expected_data(program, request)
expected.update({
'marketing_slug': program.marketing_slug,
'marketing_url': program.marketing_url,
'video': VideoSerializer(program.video).data,
'credit_redemption_overview': program.credit_redemption_overview, 'credit_redemption_overview': program.credit_redemption_overview,
'courses': ProgramCourseSerializer(
program.courses.all(),
many=True,
context={'request': request, 'program': program}
).data,
'corporate_endorsements': CorporateEndorsementSerializer(program.corporate_endorsements, many=True).data, 'corporate_endorsements': CorporateEndorsementSerializer(program.corporate_endorsements, many=True).data,
'credit_backing_organizations': MinimalOrganizationSerializer( 'credit_backing_organizations': OrganizationSerializer(
program.credit_backing_organizations, program.credit_backing_organizations,
many=True many=True
).data, ).data,
...@@ -450,80 +475,28 @@ class ProgramSerializerTests(TestCase): ...@@ -450,80 +475,28 @@ class ProgramSerializerTests(TestCase):
'job_outlook_items': [item.value for item in program.job_outlook_items.all()], 'job_outlook_items': [item.value for item in program.job_outlook_items.all()],
'languages': [serialize_language_to_code(l) for l in program.languages], 'languages': [serialize_language_to_code(l) for l in program.languages],
'weeks_to_complete': program.weeks_to_complete, 'weeks_to_complete': program.weeks_to_complete,
'max_hours_effort_per_week': None, 'max_hours_effort_per_week': program.max_hours_effort_per_week,
'min_hours_effort_per_week': None, 'min_hours_effort_per_week': program.min_hours_effort_per_week,
'overview': None, 'overview': program.overview,
'price_ranges': [], 'price_ranges': [],
'status': program.status, 'subjects': SubjectSerializer(program.subjects, many=True).data,
'subjects': [], 'transcript_languages': [serialize_language_to_code(l) for l in program.transcript_languages],
'transcript_languages': [], })
} return expected
self.assertDictEqual(serializer.data, expected)
def test_with_exclusions(self): def test_data_with_exclusions(self):
""" """
Verify we can specify program excluded_course_runs and the serializers will Verify we can specify program excluded_course_runs and the serializers will
render the course_runs with exclusions render the course_runs with exclusions
""" """
request = make_request() request = make_request()
org_list = OrganizationFactory.create_batch(1) program = self.create_program()
course_list = CourseFactory.create_batch(4)
excluded_runs = []
for course in course_list:
course_runs = CourseRunFactory.create_batch(
3,
course=course,
enrollment_start=datetime(2014, 1, 1),
start=datetime(2014, 1, 1)
)
excluded_runs.append(course_runs[0])
program = ProgramFactory(
authoring_organizations=org_list,
courses=course_list,
excluded_course_runs=excluded_runs
)
serializer = ProgramSerializer(program, context={'request': request})
expected = { excluded_course_run = program.courses.all()[0].course_runs.all()[0]
'uuid': str(program.uuid), program.excluded_course_runs.add(excluded_course_run)
'title': program.title,
'subtitle': program.subtitle,
'type': program.type.name,
'marketing_slug': program.marketing_slug,
'marketing_url': program.marketing_url,
'card_image_url': program.card_image_url,
'banner_image': {},
'video': None,
'authoring_organizations': MinimalOrganizationSerializer(program.authoring_organizations, many=True).data,
'credit_redemption_overview': program.credit_redemption_overview,
'courses': ProgramCourseSerializer(
program.courses,
many=True,
context={'request': request, 'program': program}
).data,
'corporate_endorsements': CorporateEndorsementSerializer(program.corporate_endorsements, many=True).data,
'credit_backing_organizations': MinimalOrganizationSerializer(
program.credit_backing_organizations,
many=True
).data,
'expected_learning_items': [],
'faq': FAQSerializer(program.faq, many=True).data,
'individual_endorsements': EndorsementSerializer(program.individual_endorsements, many=True).data,
'staff': PersonSerializer(program.staff, many=True).data,
'job_outlook_items': [],
'languages': [serialize_language_to_code(l) for l in program.languages],
'weeks_to_complete': program.weeks_to_complete,
'max_hours_effort_per_week': None,
'min_hours_effort_per_week': None,
'overview': None,
'price_ranges': [],
'status': program.status,
'subjects': [],
'transcript_languages': [],
}
expected = self.get_expected_data(program, request)
serializer = ProgramSerializer(program, context={'request': request})
self.assertDictEqual(serializer.data, expected) self.assertDictEqual(serializer.data, expected)
...@@ -649,27 +622,45 @@ class VideoSerializerTests(TestCase): ...@@ -649,27 +622,45 @@ class VideoSerializerTests(TestCase):
self.assertDictEqual(serializer.data, expected) self.assertDictEqual(serializer.data, expected)
class OrganizationSerializerTests(TestCase): class MinimalOrganizationSerializerTests(TestCase):
maxDiff = None serializer_class = MinimalOrganizationSerializer
def test_data(self): def create_organization(self):
organization = OrganizationFactory() return OrganizationFactory()
TAG = 'test'
organization.tags.add(TAG)
serializer = OrganizationSerializer(organization)
expected = { def get_expected_data(self, organization):
return {
'uuid': str(organization.uuid), 'uuid': str(organization.uuid),
'key': organization.key, 'key': organization.key,
'name': organization.name, 'name': organization.name,
}
def test_data(self):
organization = self.create_organization()
serializer = self.serializer_class(organization)
expected = self.get_expected_data(organization)
self.assertDictEqual(serializer.data, expected)
class OrganizationSerializerTests(MinimalOrganizationSerializerTests):
TAG = 'test-tag'
serializer_class = OrganizationSerializer
def create_organization(self):
organization = super().create_organization()
organization.tags.add(self.TAG)
return organization
def get_expected_data(self, organization):
expected = super().get_expected_data(organization)
expected.update({
'description': organization.description, 'description': organization.description,
'homepage_url': organization.homepage_url, 'homepage_url': organization.homepage_url,
'logo_image_url': organization.logo_image_url, 'logo_image_url': organization.logo_image_url,
'tags': [TAG], 'tags': [self.TAG],
'marketing_url': organization.marketing_url, 'marketing_url': organization.marketing_url,
} })
return expected
self.assertDictEqual(serializer.data, expected)
class SeatSerializerTests(TestCase): class SeatSerializerTests(TestCase):
......
...@@ -2,7 +2,7 @@ import ddt ...@@ -2,7 +2,7 @@ import ddt
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from rest_framework.test import APITestCase, APIRequestFactory from rest_framework.test import APITestCase, APIRequestFactory
from course_discovery.apps.api.serializers import ProgramSerializer, MinimalProgramSerializer from course_discovery.apps.api.serializers import ProgramSerializer
from course_discovery.apps.core.tests.factories import USER_PASSWORD, UserFactory from course_discovery.apps.core.tests.factories import USER_PASSWORD, UserFactory
from course_discovery.apps.course_metadata.choices import ProgramStatus from course_discovery.apps.course_metadata.choices import ProgramStatus
from course_discovery.apps.course_metadata.models import Program from course_discovery.apps.course_metadata.models import Program
...@@ -69,21 +69,21 @@ class ProgramViewSetTests(APITestCase): ...@@ -69,21 +69,21 @@ class ProgramViewSetTests(APITestCase):
self.assertEqual( self.assertEqual(
response.data['results'], response.data['results'],
MinimalProgramSerializer(expected, many=True, context={'request': self.request}).data ProgramSerializer(expected, many=True, context={'request': self.request}).data
) )
def test_list(self): def test_list(self):
""" Verify the endpoint returns a list of all programs. """ """ Verify the endpoint returns a list of all programs. """
expected = ProgramFactory.create_batch(3) expected = ProgramFactory.create_batch(3)
expected.reverse() expected.reverse()
self.assert_list_results(self.list_path, expected, 7) self.assert_list_results(self.list_path, expected, 40)
def test_filter_by_type(self): def test_filter_by_type(self):
""" Verify that the endpoint filters programs to those of a given type. """ """ Verify that the endpoint filters programs to those of a given type. """
program_type_name = 'foo' program_type_name = 'foo'
program = ProgramFactory(type__name=program_type_name) program = ProgramFactory(type__name=program_type_name)
url = self.list_path + '?type=' + program_type_name url = self.list_path + '?type=' + program_type_name
self.assert_list_results(url, [program], 7) self.assert_list_results(url, [program], 18)
url = self.list_path + '?type=bar' url = self.list_path + '?type=bar'
self.assert_list_results(url, [], 4) self.assert_list_results(url, [], 4)
...@@ -98,11 +98,11 @@ class ProgramViewSetTests(APITestCase): ...@@ -98,11 +98,11 @@ class ProgramViewSetTests(APITestCase):
# Create a third program, which should be filtered out. # Create a third program, which should be filtered out.
ProgramFactory() ProgramFactory()
self.assert_list_results(url, expected, 7) self.assert_list_results(url, expected, 29)
@ddt.data( @ddt.data(
(ProgramStatus.Unpublished, False, 4), (ProgramStatus.Unpublished, False, 4),
(ProgramStatus.Active, True, 7), (ProgramStatus.Active, True, 40),
) )
@ddt.unpack @ddt.unpack
def test_filter_by_marketable(self, status, is_marketable, expected_query_count): def test_filter_by_marketable(self, status, is_marketable, expected_query_count):
......
...@@ -395,6 +395,7 @@ class ProgramViewSet(viewsets.ReadOnlyModelViewSet): ...@@ -395,6 +395,7 @@ class ProgramViewSet(viewsets.ReadOnlyModelViewSet):
lookup_value_regex = '[0-9a-f-]+' lookup_value_regex = '[0-9a-f-]+'
queryset = prefetch_related_objects_for_programs(Program.objects.all()) queryset = prefetch_related_objects_for_programs(Program.objects.all())
permission_classes = (IsAuthenticated,) permission_classes = (IsAuthenticated,)
serializer_class = serializers.ProgramSerializer
filter_backends = (DjangoFilterBackend,) filter_backends = (DjangoFilterBackend,)
filter_class = filters.ProgramFilter filter_class = filters.ProgramFilter
...@@ -403,12 +404,6 @@ class ProgramViewSet(viewsets.ReadOnlyModelViewSet): ...@@ -403,12 +404,6 @@ class ProgramViewSet(viewsets.ReadOnlyModelViewSet):
context['published_course_runs_only'] = int(self.request.GET.get('published_course_runs_only', 0)) context['published_course_runs_only'] = int(self.request.GET.get('published_course_runs_only', 0))
return context return context
def get_serializer_class(self):
if self.action == 'list':
return serializers.MinimalProgramSerializer
return serializers.ProgramSerializer
def list(self, request, *args, **kwargs): def list(self, request, *args, **kwargs):
""" List all programs. """ List all programs.
--- ---
......
...@@ -239,13 +239,17 @@ class ProgramFactory(factory.django.DjangoModelFactory): ...@@ -239,13 +239,17 @@ class ProgramFactory(factory.django.DjangoModelFactory):
title = factory.Sequence(lambda n: 'test-program-{}'.format(n)) # pylint: disable=unnecessary-lambda title = factory.Sequence(lambda n: 'test-program-{}'.format(n)) # pylint: disable=unnecessary-lambda
uuid = factory.LazyFunction(uuid4) uuid = factory.LazyFunction(uuid4)
subtitle = 'test-subtitle' subtitle = FuzzyText()
type = factory.SubFactory(ProgramTypeFactory) type = factory.SubFactory(ProgramTypeFactory)
status = ProgramStatus.Unpublished status = ProgramStatus.Unpublished
marketing_slug = factory.Sequence(lambda n: 'test-slug-{}'.format(n)) # pylint: disable=unnecessary-lambda marketing_slug = factory.Sequence(lambda n: 'test-slug-{}'.format(n)) # pylint: disable=unnecessary-lambda
banner_image_url = FuzzyText(prefix='https://example.com/program/banner') banner_image_url = FuzzyText(prefix='https://example.com/program/banner')
card_image_url = FuzzyText(prefix='https://example.com/program/card') card_image_url = FuzzyText(prefix='https://example.com/program/card')
partner = factory.SubFactory(PartnerFactory) partner = factory.SubFactory(PartnerFactory)
overview = FuzzyText()
weeks_to_complete = FuzzyInteger(1)
min_hours_effort_per_week = FuzzyInteger(2)
max_hours_effort_per_week = FuzzyInteger(4)
credit_redemption_overview = FuzzyText() credit_redemption_overview = FuzzyText()
@factory.post_generation @factory.post_generation
...@@ -289,11 +293,6 @@ class ProgramFactory(factory.django.DjangoModelFactory): ...@@ -289,11 +293,6 @@ class ProgramFactory(factory.django.DjangoModelFactory):
add_m2m_data(self.individual_endorsements, extracted) add_m2m_data(self.individual_endorsements, extracted)
@factory.post_generation @factory.post_generation
def staff(self, create, extracted, **kwargs):
if create: # pragma: no cover
add_m2m_data(self.staff, extracted)
@factory.post_generation
def job_outlook_items(self, create, extracted, **kwargs): def job_outlook_items(self, create, extracted, **kwargs):
if create: # pragma: no cover if create: # pragma: no cover
add_m2m_data(self.job_outlook_items, extracted) add_m2m_data(self.job_outlook_items, extracted)
......
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