Commit b1754dbf by Mike Dikan Committed by mikedikan

Update Program Service to provide all metadata

parent 6d4ed257
......@@ -12,7 +12,8 @@ from taggit_serializer.serializers import TagListSerializerField, TaggitSerializ
from course_discovery.apps.api.fields import StdImageSerializerField, ImageField
from course_discovery.apps.catalogs.models import Catalog
from course_discovery.apps.course_metadata.models import (
Course, CourseRun, Image, Organization, Person, Prerequisite, Seat, Subject, Video, Program, ProgramType,
Course, CourseRun, Image, Organization, Person, Prerequisite, Seat, Subject, Video, Program, ProgramType, FAQ,
CorporateEndorsement, Endorsement
)
from course_discovery.apps.course_metadata.search_indexes import CourseIndex, CourseRunIndex, ProgramIndex
......@@ -92,6 +93,14 @@ class NamedModelSerializer(serializers.ModelSerializer):
fields = ('name',)
class FAQSerializer(serializers.ModelSerializer):
"""Serializer for the ``FAQ`` model."""
class Meta(object):
model = FAQ
fields = ('question', 'answer',)
class SubjectSerializer(NamedModelSerializer):
"""Serializer for the ``Subject`` model."""
......@@ -131,6 +140,33 @@ class VideoSerializer(MediaSerializer):
fields = ('src', 'description', 'image',)
class PersonSerializer(serializers.ModelSerializer):
"""Serializer for the ``Person`` model."""
class Meta(object):
model = Person
fields = ('uuid', 'given_name', 'family_name', 'bio', 'profile_image_url', 'slug',)
class EndorsementSerializer(serializers.ModelSerializer):
"""Serializer for the ``Endorsement`` model."""
endorser = PersonSerializer()
class Meta(object):
model = Endorsement
fields = ('endorser', 'quote',)
class CorporateEndorsementSerializer(serializers.ModelSerializer):
"""Serializer for the ``CorporateEndorsement`` model."""
image = ImageSerializer()
individual_endorsements = EndorsementSerializer(many=True)
class Meta(object):
model = CorporateEndorsement
fields = ('corporation_name', 'statement', 'image', 'individual_endorsements',)
class SeatSerializer(serializers.ModelSerializer):
"""Serializer for the ``Seat`` model."""
type = serializers.ChoiceField(
......@@ -150,14 +186,6 @@ class SeatSerializer(serializers.ModelSerializer):
fields = ('type', 'price', 'currency', 'upgrade_deadline', 'credit_provider', 'credit_hours',)
class PersonSerializer(serializers.ModelSerializer):
"""Serializer for the ``Person`` model."""
class Meta(object):
model = Person
fields = ('uuid', 'given_name', 'family_name', 'bio', 'profile_image_url', 'slug',)
class OrganizationSerializer(TaggitSerializer, serializers.ModelSerializer):
"""Serializer for the ``Organization`` model."""
tags = TagListSerializerField()
......@@ -317,6 +345,23 @@ class ProgramSerializer(serializers.ModelSerializer):
authoring_organizations = OrganizationSerializer(many=True)
type = serializers.SlugRelatedField(slug_field='name', queryset=ProgramType.objects.all())
banner_image = StdImageSerializerField()
video = VideoSerializer()
expected_learning_items = serializers.SlugRelatedField(many=True, read_only=True, slug_field='value')
faq = FAQSerializer(many=True)
credit_backing_organizations = OrganizationSerializer(many=True)
corporate_endorsements = CorporateEndorsementSerializer(many=True)
job_outlook_items = serializers.SlugRelatedField(many=True, read_only=True, slug_field='value')
individual_endorsements = EndorsementSerializer(many=True)
languages = serializers.SlugRelatedField(
many=True, read_only=True, slug_field='code',
help_text=_('Languages that course runs in this program are offered in.'),
)
transcript_languages = serializers.SlugRelatedField(
many=True, read_only=True, slug_field='code',
help_text=_('Languages that course runs in this program have available transcripts in.'),
)
subjects = SubjectSerializer(many=True)
staff = PersonSerializer(many=True)
def get_courses(self, program):
course_serializer = ProgramCourseSerializer(
......@@ -331,9 +376,14 @@ class ProgramSerializer(serializers.ModelSerializer):
class Meta:
model = Program
fields = ('uuid', 'title', 'subtitle', 'type', 'marketing_slug', 'marketing_url', 'card_image_url',
'banner_image', 'banner_image_url', 'authoring_organizations', 'credit_redemption_overview',
'courses',)
fields = (
'uuid', 'title', 'subtitle', 'type', 'status', 'marketing_slug', 'marketing_url', 'courses',
'overview', 'weeks_to_complete', 'min_hours_effort_per_week', 'max_hours_effort_per_week',
'authoring_organizations', 'banner_image', 'banner_image_url', 'card_image_url', 'video',
'expected_learning_items', 'faq', 'credit_backing_organizations', 'corporate_endorsements',
'job_outlook_items', 'individual_endorsements', 'languages', 'transcript_languages', 'subjects',
'price_ranges', 'staff', 'credit_redemption_overview'
)
read_only_fields = ('uuid', 'marketing_url', 'banner_image')
......
......@@ -13,7 +13,8 @@ from course_discovery.apps.api.serializers import (
SubjectSerializer, PrerequisiteSerializer, VideoSerializer, OrganizationSerializer, SeatSerializer,
PersonSerializer, AffiliateWindowSerializer, ContainedCourseRunsSerializer, CourseRunSearchSerializer,
ProgramSerializer, ProgramSearchSerializer, ProgramCourseSerializer, NestedProgramSerializer,
CourseRunWithProgramsSerializer, CourseWithProgramsSerializer
CourseRunWithProgramsSerializer, CourseWithProgramsSerializer, CorporateEndorsementSerializer,
FAQSerializer, EndorsementSerializer
)
from course_discovery.apps.catalogs.tests.factories import CatalogFactory
from course_discovery.apps.core.models import User
......@@ -22,7 +23,8 @@ from course_discovery.apps.core.tests.helpers import make_image_file
from course_discovery.apps.course_metadata.models import CourseRun, Program
from course_discovery.apps.course_metadata.tests.factories import (
CourseFactory, CourseRunFactory, SubjectFactory, PrerequisiteFactory, ImageFactory, VideoFactory,
OrganizationFactory, PersonFactory, SeatFactory, ProgramFactory
OrganizationFactory, PersonFactory, SeatFactory, ProgramFactory, CorporateEndorsementFactory, EndorsementFactory,
JobOutlookItemFactory, ExpectedLearningItemFactory
)
......@@ -39,6 +41,18 @@ def make_request():
return request
def serialize_datetime(d):
return d.strftime('%Y-%m-%dT%H:%M:%S') if d else None
def serialize_language(language):
return language.macrolanguage
def serialize_language_to_code(language):
return language.code
class CatalogSerializerTests(TestCase):
def test_data(self):
user = UserFactory()
......@@ -207,11 +221,26 @@ class ProgramCourseSerializerTests(TestCase):
class ProgramSerializerTests(TestCase):
def test_data(self):
request = make_request()
org_list = OrganizationFactory.create_batch(1)
course_list = CourseFactory.create_batch(3)
program = ProgramFactory(authoring_organizations=org_list, courses=course_list)
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(
authoring_organizations=org_list,
courses=course_list,
credit_backing_organizations=org_list,
corporate_endorsements=corporate_endorsements,
individual_endorsements=individual_endorsements,
expected_learning_items=expected_learning_items,
staff=staff,
job_outlook_items=job_outlook_items,
)
program.banner_image = make_image_file('test_banner.jpg')
program.save()
serializer = ProgramSerializer(program, context={'request': request})
......@@ -236,6 +265,7 @@ class ProgramSerializerTests(TestCase):
'marketing_url': program.marketing_url,
'card_image_url': program.card_image_url,
'banner_image_url': program.banner_image_url,
'video': None,
'banner_image': expected_banner_image_urls,
'authoring_organizations': OrganizationSerializer(program.authoring_organizations, many=True).data,
'credit_redemption_overview': program.credit_redemption_overview,
......@@ -244,6 +274,25 @@ class ProgramSerializerTests(TestCase):
many=True,
context={'request': request, 'program': program}
).data,
'corporate_endorsements': CorporateEndorsementSerializer(program.corporate_endorsements, many=True).data,
'credit_backing_organizations': OrganizationSerializer(
program.credit_backing_organizations,
many=True
).data,
'expected_learning_items': [item.value for item in program.expected_learning_items.all()],
'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': [item.value for item in program.job_outlook_items.all()],
'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': [],
}
self.assertDictEqual(serializer.data, expected)
......@@ -278,6 +327,7 @@ class ProgramSerializerTests(TestCase):
'card_image_url': program.card_image_url,
'banner_image': {},
'banner_image_url': program.banner_image_url,
'video': None,
'authoring_organizations': OrganizationSerializer(program.authoring_organizations, many=True).data,
'credit_redemption_overview': program.credit_redemption_overview,
'courses': ProgramCourseSerializer(
......@@ -285,6 +335,25 @@ class ProgramSerializerTests(TestCase):
many=True,
context={'request': request, 'program': program}
).data,
'corporate_endorsements': CorporateEndorsementSerializer(program.corporate_endorsements, many=True).data,
'credit_backing_organizations': OrganizationSerializer(
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': [],
}
self.assertDictEqual(serializer.data, expected)
......@@ -347,6 +416,24 @@ class ImageSerializerTests(TestCase):
self.assertDictEqual(serializer.data, expected)
class CorporateEndorsementSerializerTests(TestCase):
def test_data(self):
corporate_endorsement = CorporateEndorsementFactory()
serializer = CorporateEndorsementSerializer(corporate_endorsement)
expected = {
'corporation_name': corporate_endorsement.corporation_name,
'statement': corporate_endorsement.statement,
'image': ImageSerializer(corporate_endorsement.image).data,
'individual_endorsements': EndorsementSerializer(
corporate_endorsement.individual_endorsements,
many=True
).data
}
self.assertDictEqual(serializer.data, expected)
class NestedProgramSerializerTests(TestCase):
def test_data(self):
program = ProgramFactory()
......@@ -459,11 +546,6 @@ class AffiliateWindowSerializerTests(TestCase):
class CourseRunSearchSerializerTests(TestCase):
def serialize_datetime(self, d):
return d.strftime('%Y-%m-%dT%H:%M:%S') if d else None
def serialize_language(self, language):
return language.macrolanguage
def test_data(self):
course_run = CourseRunFactory()
......@@ -471,16 +553,16 @@ class CourseRunSearchSerializerTests(TestCase):
course_run_key = CourseKey.from_string(course_run.key)
expected = {
'transcript_languages': [self.serialize_language(l) for l in course_run.transcript_languages.all()],
'transcript_languages': [serialize_language(l) for l in course_run.transcript_languages.all()],
'short_description': course_run.short_description,
'start': self.serialize_datetime(course_run.start),
'end': self.serialize_datetime(course_run.end),
'enrollment_start': self.serialize_datetime(course_run.enrollment_start),
'enrollment_end': self.serialize_datetime(course_run.enrollment_end),
'start': serialize_datetime(course_run.start),
'end': serialize_datetime(course_run.end),
'enrollment_start': serialize_datetime(course_run.enrollment_start),
'enrollment_end': serialize_datetime(course_run.enrollment_end),
'key': course_run.key,
'marketing_url': course_run.marketing_url,
'pacing_type': course_run.pacing_type,
'language': self.serialize_language(course_run.language),
'language': serialize_language(course_run.language),
'full_description': course_run.full_description,
'title': course_run.title,
'content_type': 'courserun',
......
from django.core.urlresolvers import reverse
from rest_framework.test import APITestCase
from rest_framework.test import APITestCase, APIRequestFactory
from course_discovery.apps.api.serializers import ProgramSerializer
from course_discovery.apps.core.tests.factories import USER_PASSWORD, UserFactory
......@@ -14,6 +14,8 @@ class ProgramViewSetTests(APITestCase):
super(ProgramViewSetTests, self).setUp()
self.user = UserFactory(is_staff=True, is_superuser=True)
self.client.login(username=self.user.username, password=USER_PASSWORD)
self.request = APIRequestFactory().get('/')
self.request.user = self.user
self.program = ProgramFactory()
def test_authentication(self):
......@@ -31,7 +33,7 @@ class ProgramViewSetTests(APITestCase):
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data, ProgramSerializer(self.program).data)
self.assertEqual(response.data, ProgramSerializer(self.program, context={'request': self.request}).data)
def test_list(self):
""" Verify the endpoint returns a list of all programs. """
......@@ -39,7 +41,10 @@ class ProgramViewSetTests(APITestCase):
response = self.client.get(self.list_path)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data['results'], ProgramSerializer(Program.objects.all(), many=True).data)
self.assertEqual(
response.data['results'],
ProgramSerializer(Program.objects.all(), many=True, context={'request': self.request}).data
)
def test_filter_by_type(self):
""" Verify that the endpoint filters programs to those of a given type. """
......@@ -49,7 +54,10 @@ class ProgramViewSetTests(APITestCase):
self.program.save() # pylint: disable=no-member
response = self.client.get(url + 'foo')
self.assertEqual(response.data['results'][0], ProgramSerializer(Program.objects.get()).data)
self.assertEqual(
response.data['results'][0],
ProgramSerializer(Program.objects.get(), context={'request': self.request}).data
)
response = self.client.get(url + 'bar')
self.assertEqual(response.data['results'], [])
......@@ -65,4 +73,7 @@ class ProgramViewSetTests(APITestCase):
ProgramFactory()
response = self.client.get(url + ','.join(uuids))
self.assertEqual(response.data['results'], ProgramSerializer(programs, many=True).data)
self.assertEqual(
response.data['results'],
ProgramSerializer(programs, many=True, context={'request': self.request}).data
)
......@@ -617,7 +617,7 @@ class Program(TimeStampedModel):
@property
def languages(self):
return set([course_run.language for course_run in self.course_runs])
return set(course_run.language for course_run in self.course_runs if course_run.language is not None)
@property
def transcript_languages(self):
......@@ -650,6 +650,8 @@ class Program(TimeStampedModel):
@property
def start(self):
""" Start datetime, calculated by determining the earliest start datetime of all related course runs. """
if len(self.course_runs) == 0:
return None
return min([course_run.start for course_run in self.course_runs])
@property
......
......@@ -186,6 +186,50 @@ class ProgramTypeFactory(factory.django.DjangoModelFactory):
add_m2m_data(self.applicable_seat_types, extracted)
class EndorsementFactory(factory.django.DjangoModelFactory):
class Meta(object):
model = Endorsement
endorser = factory.SubFactory(PersonFactory)
quote = FuzzyText()
class CorporateEndorsementFactory(factory.django.DjangoModelFactory):
class Meta(object):
model = CorporateEndorsement
corporation_name = FuzzyText()
statement = FuzzyText()
image = factory.SubFactory(ImageFactory)
@factory.post_generation
def individual_endorsements(self, create, extracted, **kwargs):
if create: # pragma: no cover
add_m2m_data(self.individual_endorsements, extracted)
class JobOutlookItemFactory(factory.django.DjangoModelFactory):
class Meta(object):
model = JobOutlookItem
value = FuzzyText()
class FAQFactory(factory.django.DjangoModelFactory):
class Meta(object):
model = FAQ
question = FuzzyText()
answer = FuzzyText()
class ExpectedLearningItemFactory(factory.django.DjangoModelFactory):
class Meta(object):
model = ExpectedLearningItem
value = FuzzyText()
class ProgramFactory(factory.django.DjangoModelFactory):
class Meta(object):
model = Program
......@@ -216,6 +260,41 @@ class ProgramFactory(factory.django.DjangoModelFactory):
if create: # pragma: no cover
add_m2m_data(self.authoring_organizations, extracted)
@factory.post_generation
def corporate_endorsements(self, create, extracted, **kwargs):
if create: # pragma: no cover
add_m2m_data(self.corporate_endorsements, extracted)
@factory.post_generation
def credit_backing_organizations(self, create, extracted, **kwargs):
if create: # pragma: no cover
add_m2m_data(self.credit_backing_organizations, extracted)
@factory.post_generation
def expected_learning_items(self, create, extracted, **kwargs):
if create: # pragma: no cover
add_m2m_data(self.expected_learning_items, extracted)
@factory.post_generation
def faq(self, create, extracted, **kwargs):
if create: # pragma: no cover
add_m2m_data(self.faq, extracted)
@factory.post_generation
def individual_endorsements(self, create, extracted, **kwargs):
if create: # pragma: no cover
add_m2m_data(self.individual_endorsements, extracted)
@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):
if create: # pragma: no cover
add_m2m_data(self.job_outlook_items, extracted)
class AbstractSocialNetworkModelFactory(factory.DjangoModelFactory):
type = FuzzyChoice([name for name, __ in AbstractSocialNetworkModel.SOCIAL_NETWORK_CHOICES])
......
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