Commit c7179fce by Clinton Blackburn Committed by GitHub

Merge pull request #180 from edx/clintonb/api-updates

API Updates
parents bdbdb5b9 30cc8db5
...@@ -39,11 +39,15 @@ COURSE_RUN_FACET_FIELD_QUERIES = { ...@@ -39,11 +39,15 @@ COURSE_RUN_FACET_FIELD_QUERIES = {
'availability_archived': {'query': 'end:<=now'}, 'availability_archived': {'query': 'end:<=now'},
} }
COURSE_RUN_SEARCH_FIELDS = ( COURSE_RUN_SEARCH_FIELDS = (
'key', 'title', 'short_description', 'full_description', 'start', 'end', 'enrollment_start', 'enrollment_end', 'text', 'key', 'title', 'short_description', 'full_description', 'start', 'end', 'enrollment_start',
'pacing_type', 'language', 'transcript_languages', 'marketing_url', 'content_type', 'org', 'number', 'seat_types', 'enrollment_end', 'pacing_type', 'language', 'transcript_languages', 'marketing_url', 'content_type', 'org',
'image_url', 'type', 'text', 'number', 'seat_types', 'image_url', 'type', 'level_type', 'availability',
) )
PROGRAM_FACET_FIELD_OPTIONS = {
'category': {},
}
PROGRAM_SEARCH_FIELDS = ( PROGRAM_SEARCH_FIELDS = (
'uuid', 'title', 'subtitle', 'category', 'marketing_url', 'organizations', 'content_type', 'image_url', 'text', 'uuid', 'title', 'subtitle', 'category', 'marketing_url', 'organizations', 'content_type', 'image_url', 'text',
) )
...@@ -194,6 +198,7 @@ class CourseRunSerializer(TimestampModelSerializer): ...@@ -194,6 +198,7 @@ class CourseRunSerializer(TimestampModelSerializer):
instructors = PersonSerializer(many=True) instructors = PersonSerializer(many=True)
staff = PersonSerializer(many=True) staff = PersonSerializer(many=True)
marketing_url = serializers.SerializerMethodField() marketing_url = serializers.SerializerMethodField()
level_type = serializers.SlugRelatedField(read_only=True, slug_field='name')
class Meta(object): class Meta(object):
model = CourseRun model = CourseRun
...@@ -201,7 +206,7 @@ class CourseRunSerializer(TimestampModelSerializer): ...@@ -201,7 +206,7 @@ class CourseRunSerializer(TimestampModelSerializer):
'course', 'key', 'title', 'short_description', 'full_description', 'start', 'end', 'course', 'key', 'title', 'short_description', 'full_description', 'start', 'end',
'enrollment_start', 'enrollment_end', 'announcement', 'image', 'video', 'seats', 'enrollment_start', 'enrollment_end', 'announcement', 'image', 'video', 'seats',
'content_language', 'transcript_languages', 'instructors', 'staff', 'content_language', 'transcript_languages', 'instructors', 'staff',
'pacing_type', 'min_effort', 'max_effort', 'modified', 'marketing_url', 'pacing_type', 'min_effort', 'max_effort', 'modified', 'marketing_url', 'level_type', 'availability',
) )
def get_marketing_url(self, obj): def get_marketing_url(self, obj):
...@@ -479,6 +484,11 @@ class CourseFacetSerializer(BaseHaystackFacetSerializer): ...@@ -479,6 +484,11 @@ class CourseFacetSerializer(BaseHaystackFacetSerializer):
class CourseRunSearchSerializer(HaystackSerializer): class CourseRunSearchSerializer(HaystackSerializer):
availability = serializers.SerializerMethodField()
def get_availability(self, result):
return result.object.availability
class Meta: class Meta:
field_aliases = COMMON_SEARCH_FIELD_ALIASES field_aliases = COMMON_SEARCH_FIELD_ALIASES
fields = COURSE_RUN_SEARCH_FIELDS fields = COURSE_RUN_SEARCH_FIELDS
...@@ -499,6 +509,7 @@ class CourseRunFacetSerializer(BaseHaystackFacetSerializer): ...@@ -499,6 +509,7 @@ class CourseRunFacetSerializer(BaseHaystackFacetSerializer):
class ProgramSearchSerializer(HaystackSerializer): class ProgramSearchSerializer(HaystackSerializer):
class Meta: class Meta:
field_aliases = COMMON_SEARCH_FIELD_ALIASES field_aliases = COMMON_SEARCH_FIELD_ALIASES
field_options = PROGRAM_FACET_FIELD_OPTIONS
fields = PROGRAM_SEARCH_FIELDS fields = PROGRAM_SEARCH_FIELDS
ignore_fields = COMMON_IGNORED_FIELDS ignore_fields = COMMON_IGNORED_FIELDS
index_classes = [ProgramIndex] index_classes = [ProgramIndex]
...@@ -509,6 +520,7 @@ class ProgramFacetSerializer(BaseHaystackFacetSerializer): ...@@ -509,6 +520,7 @@ class ProgramFacetSerializer(BaseHaystackFacetSerializer):
class Meta: class Meta:
field_aliases = COMMON_SEARCH_FIELD_ALIASES field_aliases = COMMON_SEARCH_FIELD_ALIASES
field_options = PROGRAM_FACET_FIELD_OPTIONS
fields = PROGRAM_SEARCH_FIELDS fields = PROGRAM_SEARCH_FIELDS
ignore_fields = COMMON_IGNORED_FIELDS ignore_fields = COMMON_IGNORED_FIELDS
index_classes = [ProgramIndex] index_classes = [ProgramIndex]
...@@ -517,7 +529,7 @@ class ProgramFacetSerializer(BaseHaystackFacetSerializer): ...@@ -517,7 +529,7 @@ class ProgramFacetSerializer(BaseHaystackFacetSerializer):
class AggregateSearchSerializer(HaystackSerializer): class AggregateSearchSerializer(HaystackSerializer):
class Meta: class Meta:
field_aliases = COMMON_SEARCH_FIELD_ALIASES field_aliases = COMMON_SEARCH_FIELD_ALIASES
fields = COURSE_RUN_SEARCH_FIELDS fields = COURSE_RUN_SEARCH_FIELDS + PROGRAM_SEARCH_FIELDS
ignore_fields = COMMON_IGNORED_FIELDS ignore_fields = COMMON_IGNORED_FIELDS
serializers = { serializers = {
CourseRunIndex: CourseRunSearchSerializer, CourseRunIndex: CourseRunSearchSerializer,
...@@ -531,7 +543,7 @@ class AggregateFacetSearchSerializer(BaseHaystackFacetSerializer): ...@@ -531,7 +543,7 @@ class AggregateFacetSearchSerializer(BaseHaystackFacetSerializer):
class Meta: class Meta:
field_aliases = COMMON_SEARCH_FIELD_ALIASES field_aliases = COMMON_SEARCH_FIELD_ALIASES
field_options = COURSE_RUN_FACET_FIELD_OPTIONS field_options = {**COURSE_RUN_FACET_FIELD_OPTIONS, **PROGRAM_FACET_FIELD_OPTIONS}
field_queries = COURSE_RUN_FACET_FIELD_QUERIES field_queries = COURSE_RUN_FACET_FIELD_QUERIES
ignore_fields = COMMON_IGNORED_FIELDS ignore_fields = COMMON_IGNORED_FIELDS
serializers = { serializers = {
......
...@@ -152,6 +152,8 @@ class CourseRunSerializerTests(TestCase): ...@@ -152,6 +152,8 @@ class CourseRunSerializerTests(TestCase):
'utm_medium': request.user.referral_tracking_id, 'utm_medium': request.user.referral_tracking_id,
}) })
), ),
'level_type': course_run.level_type.name,
'availability': course_run.availability,
} }
self.assertDictEqual(serializer.data, expected) self.assertDictEqual(serializer.data, expected)
...@@ -375,6 +377,8 @@ class CourseRunSearchSerializerTests(TestCase): ...@@ -375,6 +377,8 @@ class CourseRunSearchSerializerTests(TestCase):
'seat_types': course_run.seat_types, 'seat_types': course_run.seat_types,
'image_url': course_run.image_url, 'image_url': course_run.image_url,
'type': course_run.type, 'type': course_run.type,
'level_type': course_run.level_type.name,
'availability': course_run.availability,
} }
self.assertDictEqual(serializer.data, expected) self.assertDictEqual(serializer.data, expected)
...@@ -398,7 +402,7 @@ class ProgramSearchSerializerTests(TestCase): ...@@ -398,7 +402,7 @@ class ProgramSearchSerializerTests(TestCase):
'category': program.category, 'category': program.category,
'marketing_url': program.marketing_url, 'marketing_url': program.marketing_url,
'organizations': [OrganizationsMixin.format_organization(organization)], 'organizations': [OrganizationsMixin.format_organization(organization)],
'content_type': 'program_{category}'.format(category=program.category), 'content_type': 'program',
'image_url': program.image_url, 'image_url': program.image_url,
} }
self.assertDictEqual(serializer.data, expected) self.assertDictEqual(serializer.data, expected)
...@@ -356,6 +356,24 @@ class CourseRun(TimeStampedModel): ...@@ -356,6 +356,24 @@ class CourseRun(TimeStampedModel):
return None return None
@property
def level_type(self):
return self.course.level_type
@property
def availability(self):
now = datetime.datetime.now(pytz.UTC)
upcoming_cutoff = now + datetime.timedelta(days=60)
if self.end and self.end <= now:
return _('Archived')
elif self.start and self.end and (self.start <= now < self.end):
return _('Current')
elif self.start and (now < self.start < upcoming_cutoff):
return _('Starting Soon')
else:
return _('Upcoming')
@classmethod @classmethod
def search(cls, query): def search(cls, query):
""" Queries the search index. """ Queries the search index.
......
...@@ -39,6 +39,7 @@ class BaseCourseIndex(OrganizationsMixin, BaseIndex): ...@@ -39,6 +39,7 @@ class BaseCourseIndex(OrganizationsMixin, BaseIndex):
full_description = indexes.CharField(model_attr='full_description', null=True) full_description = indexes.CharField(model_attr='full_description', null=True)
subjects = indexes.MultiValueField(faceted=True) subjects = indexes.MultiValueField(faceted=True)
organizations = indexes.MultiValueField(faceted=True) organizations = indexes.MultiValueField(faceted=True)
level_type = indexes.CharField(model_attr='level_type__name', null=True, faceted=True)
def prepare_subjects(self, obj): def prepare_subjects(self, obj):
return [subject.name for subject in obj.subjects.all()] return [subject.name for subject in obj.subjects.all()]
...@@ -47,7 +48,6 @@ class BaseCourseIndex(OrganizationsMixin, BaseIndex): ...@@ -47,7 +48,6 @@ class BaseCourseIndex(OrganizationsMixin, BaseIndex):
class CourseIndex(BaseCourseIndex, indexes.Indexable): class CourseIndex(BaseCourseIndex, indexes.Indexable):
model = Course model = Course
level_type = indexes.CharField(model_attr='level_type__name', null=True, faceted=True)
course_runs = indexes.MultiValueField() course_runs = indexes.MultiValueField()
expected_learning_items = indexes.MultiValueField() expected_learning_items = indexes.MultiValueField()
...@@ -114,6 +114,3 @@ class ProgramIndex(OrganizationsMixin, BaseIndex, indexes.Indexable): ...@@ -114,6 +114,3 @@ class ProgramIndex(OrganizationsMixin, BaseIndex, indexes.Indexable):
marketing_url = indexes.CharField(model_attr='marketing_url', null=True) marketing_url = indexes.CharField(model_attr='marketing_url', null=True)
organizations = indexes.MultiValueField(faceted=True) organizations = indexes.MultiValueField(faceted=True)
image_url = indexes.CharField(model_attr='image_url', null=True) image_url = indexes.CharField(model_attr='image_url', null=True)
def prepare_content_type(self, obj):
return 'program_{category}'.format(category=obj.category)
...@@ -3,9 +3,11 @@ import datetime ...@@ -3,9 +3,11 @@ import datetime
import ddt import ddt
import mock import mock
import pytz import pytz
from django.db import IntegrityError from dateutil.parser import parse
from django.conf import settings from django.conf import settings
from django.db import IntegrityError
from django.test import TestCase from django.test import TestCase
from freezegun import freeze_time
from course_discovery.apps.core.utils import SearchQuerySetWrapper from course_discovery.apps.core.utils import SearchQuerySetWrapper
from course_discovery.apps.course_metadata.models import ( from course_discovery.apps.course_metadata.models import (
...@@ -178,6 +180,30 @@ class CourseRunTests(TestCase): ...@@ -178,6 +180,30 @@ class CourseRunTests(TestCase):
factories.SeatFactory(course_run=self.course_run, type=seat_type) factories.SeatFactory(course_run=self.course_run, type=seat_type)
self.assert_course_run_has_no_type(self.course_run, set([seat_type])) self.assert_course_run_has_no_type(self.course_run, set([seat_type]))
def test_level_type(self):
""" Verify the property returns the associated Course's level type. """
self.assertEqual(self.course_run.level_type, self.course_run.course.level_type)
@freeze_time('2016-06-21 00:00:00Z')
@ddt.data(
(None, None, 'Upcoming'),
('2030-01-01 00:00:00Z', None, 'Upcoming'),
(None, '2016-01-01 00:00:00Z', 'Archived'),
('2015-01-01 00:00:00Z', '2016-01-01 00:00:00Z', 'Archived'),
('2016-01-01 00:00:00Z', '2017-01-01 00:00:00Z', 'Current'),
('2016-07-21 00:00:00Z', '2017-01-01 00:00:00Z', 'Starting Soon'),
)
@ddt.unpack
def test_availability(self, start, end, expected_availability):
""" Verify the property returns the appropriate availability string based on the start/end dates. """
if start:
start = parse(start)
if end:
end = parse(end)
course_run = factories.CourseRunFactory(start=start, end=end)
self.assertEqual(course_run.availability, expected_availability)
class OrganizationTests(TestCase): class OrganizationTests(TestCase):
""" Tests for the `Organization` model. """ """ Tests for the `Organization` model. """
...@@ -272,6 +298,7 @@ class ProgramTests(TestCase): ...@@ -272,6 +298,7 @@ class ProgramTests(TestCase):
class PersonSocialNetworkTests(TestCase): class PersonSocialNetworkTests(TestCase):
"""Tests of the PersonSocialNetwork model.""" """Tests of the PersonSocialNetwork model."""
def setUp(self): def setUp(self):
super(PersonSocialNetworkTests, self).setUp() super(PersonSocialNetworkTests, self).setUp()
self.network = factories.PersonSocialNetworkFactory() self.network = factories.PersonSocialNetworkFactory()
...@@ -294,6 +321,7 @@ class PersonSocialNetworkTests(TestCase): ...@@ -294,6 +321,7 @@ class PersonSocialNetworkTests(TestCase):
class CourseSocialNetworkTests(TestCase): class CourseSocialNetworkTests(TestCase):
"""Tests of the CourseSocialNetwork model.""" """Tests of the CourseSocialNetwork model."""
def setUp(self): def setUp(self):
super(CourseSocialNetworkTests, self).setUp() super(CourseSocialNetworkTests, self).setUp()
self.network = factories.CourseRunSocialNetworkFactory() self.network = factories.CourseRunSocialNetworkFactory()
......
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