Commit 3d84e292 by Nimisha Asthagiri

Update Course Catalog API to include About Overview field.

parent 765f5285
......@@ -268,8 +268,8 @@ class CachingDescriptorSystem(MakoDescriptorSystem, EditInfoRuntimeMixin):
)
if parent_url:
parent = self._convert_reference_to_key(parent_url)
if not parent and category != 'course':
# try looking it up just-in-time (but not if we're working with a root node (course).
if not parent and category not in _DETACHED_CATEGORIES + ['course']:
# try looking it up just-in-time (but not if we're working with a detached block).
parent = self.modulestore.get_parent_location(
as_published(location),
ModuleStoreEnum.RevisionOption.published_only if location.revision is None
......
......@@ -7,6 +7,8 @@ import urllib
from django.core.urlresolvers import reverse
from rest_framework import serializers
from openedx.core.djangoapps.models.course_details import CourseDetails
class _MediaSerializer(serializers.Serializer): # pylint: disable=abstract-method
"""
......@@ -35,7 +37,8 @@ class _CourseApiMediaCollectionSerializer(serializers.Serializer): # pylint: di
class CourseSerializer(serializers.Serializer): # pylint: disable=abstract-method
"""
Serializer for Course objects
Serializer for Course objects providing minimal data about the course.
Compare this with CourseDetailSerializer.
"""
blocks_url = serializers.SerializerMethodField()
......@@ -62,3 +65,27 @@ class CourseSerializer(serializers.Serializer): # pylint: disable=abstract-meth
urllib.urlencode({'course_id': course_overview.id}),
])
return self.context['request'].build_absolute_uri(base_url)
class CourseDetailSerializer(CourseSerializer): # pylint: disable=abstract-method
"""
Serializer for Course objects providing additional details about the
course.
This serializer makes additional database accesses (to the modulestore) and
returns more data (including 'overview' text). Therefore, for performance
and bandwidth reasons, it is expected that this serializer is used only
when serializing a single course, and not for serializing a list of
courses.
"""
overview = serializers.SerializerMethodField()
def get_overview(self, course_overview):
"""
Get the representation for SerializerMethodField `overview`
"""
# Note: This makes a call to the modulestore, unlike the other
# fields from CourseSerializer, which get their data
# from the CourseOverview object in SQL.
return CourseDetails.fetch_about_attribute(course_overview.id, 'overview')
"""
Test data created by CourseSerializer
Test data created by CourseSerializer and CourseDetailSerializer
"""
from datetime import datetime
......@@ -9,44 +9,30 @@ from openedx.core.djangoapps.content.course_overviews.models import CourseOvervi
from rest_framework.test import APIRequestFactory
from rest_framework.request import Request
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xblock.core import XBlock
from xmodule.course_module import DEFAULT_START_DATE
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import check_mongo_calls
from ..serializers import CourseSerializer
from ..serializers import CourseSerializer, CourseDetailSerializer
from .mixins import CourseApiFactoryMixin
class TestCourseSerializerFields(CourseApiFactoryMixin, ModuleStoreTestCase):
class TestCourseSerializer(CourseApiFactoryMixin, ModuleStoreTestCase):
"""
Test variations of start_date field responses
Test CourseSerializer
"""
expected_mongo_calls = 0
maxDiff = 5000 # long enough to show mismatched dicts, in case of error
serializer_class = CourseSerializer
def setUp(self):
super(TestCourseSerializerFields, self).setUp()
super(TestCourseSerializer, self).setUp()
self.staff_user = self.create_user('staff', is_staff=True)
self.honor_user = self.create_user('honor', is_staff=False)
self.request_factory = APIRequestFactory()
def _get_request(self, user=None):
"""
Build a Request object for the specified user.
"""
if user is None:
user = self.honor_user
request = Request(self.request_factory.get('/'))
request.user = user
return request
def _get_result(self, course):
"""
Return the CourseSerializer for the specified course.
"""
course_overview = CourseOverview.get_from_id(course.id)
return CourseSerializer(course_overview, context={'request': self._get_request()}).data
def test_basic(self):
expected_data = {
self.expected_data = {
'course_id': u'edX/toy/2012_Fall',
'name': u'Toy Course',
'number': u'toy',
......@@ -69,10 +55,30 @@ class TestCourseSerializerFields(CourseApiFactoryMixin, ModuleStoreTestCase):
'blocks_url': u'http://testserver/api/courses/v1/blocks/?course_id=edX%2Ftoy%2F2012_Fall',
'effort': u'6 hours',
}
def _get_request(self, user=None):
"""
Build a Request object for the specified user.
"""
if user is None:
user = self.honor_user
request = Request(self.request_factory.get('/'))
request.user = user
return request
def _get_result(self, course):
"""
Return the CourseSerializer for the specified course.
"""
course_overview = CourseOverview.get_from_id(course.id)
return self.serializer_class(course_overview, context={'request': self._get_request()}).data
def test_basic(self):
course = self.create_course()
CourseDetails.update_about_video(course, 'test_youtube_id', self.staff_user.id) # pylint: disable=no-member
result = self._get_result(course)
self.assertDictEqual(result, expected_data)
with check_mongo_calls(self.expected_mongo_calls):
result = self._get_result(course)
self.assertDictEqual(result, self.expected_data)
def test_advertised_start(self):
course = self.create_course(
......@@ -91,3 +97,23 @@ class TestCourseSerializerFields(CourseApiFactoryMixin, ModuleStoreTestCase):
self.assertEqual(result['course_id'], u'edX/custom/2012_Fall')
self.assertEqual(result['start_type'], u'empty')
self.assertIsNone(result['start_display'])
class TestCourseDetailSerializer(TestCourseSerializer): # pylint: disable=test-inherits-tests
"""
Test CourseDetailSerializer by rerunning all the tests
in TestCourseSerializer, but with the
CourseDetailSerializer serializer class.
"""
# 1 mongo call is made to get the course About overview text.
expected_mongo_calls = 1
serializer_class = CourseDetailSerializer
def setUp(self):
super(TestCourseDetailSerializer, self).setUp()
# update the expected_data to include the 'overview' data.
about_descriptor = XBlock.load_class('about')
overview_template = about_descriptor.get_template('overview.yaml')
self.expected_data['overview'] = overview_template.get('data')
......@@ -9,7 +9,7 @@ from openedx.core.lib.api.paginators import NamespacedPageNumberPagination
from openedx.core.lib.api.view_utils import view_auth_classes, DeveloperErrorViewMixin
from .api import course_detail, list_courses
from .forms import CourseDetailGetForm, CourseListGetForm
from .serializers import CourseSerializer
from .serializers import CourseSerializer, CourseDetailSerializer
@view_auth_classes(is_authenticated=False)
......@@ -41,6 +41,9 @@ class CourseDetailView(DeveloperErrorViewMixin, RetrieveAPIView):
* name: Name of the course
* number: Catalog number of the course
* org: Name of the organization that owns the course
* overview: A possibly verbose HTML textual description of the course.
Note: this field is only included in the Course Detail view, not
the Course List view.
* short_description: A textual description of the course
* start: Date the course begins
* start_display: Readably formatted start of the course
......@@ -83,13 +86,14 @@ class CourseDetailView(DeveloperErrorViewMixin, RetrieveAPIView):
"name": "Example Course",
"number": "example",
"org": "edX",
"overview: "<p>A verbose description of the course.</p>"
"start": "2015-07-17T12:00:00Z",
"start_display": "July 17, 2015",
"start_type": "timestamp"
}
"""
serializer_class = CourseSerializer
serializer_class = CourseDetailSerializer
def get_object(self):
"""
......
......@@ -28,8 +28,6 @@ class FormTestMixin(object):
Check that the form returns the expected data
"""
form = self.get_form(expected_valid=True)
print 'form.cleaned_data: ' + unicode(form.cleaned_data)
print 'expected_cleaned_data: ' + unicode(expected_cleaned_data)
self.assertDictEqual(form.cleaned_data, expected_cleaned_data)
def assert_field_value(self, field, expected_value):
......
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