From 880da5a09dce2502c045c3586681ffc41ea25f44 Mon Sep 17 00:00:00 2001 From: Clinton Blackburn Date: Mon, 3 Aug 2015 08:48:34 -0400 Subject: [PATCH] Exposing course name via Commerce API The course now includes a read-only name attribute. XCOM-536 --- lms/djangoapps/commerce/api/v1/models.py | 17 +++++- lms/djangoapps/commerce/api/v1/serializers.py | 1 + .../commerce/api/v1/tests/test_views.py | 53 ++++++++----------- 3 files changed, 39 insertions(+), 32 deletions(-) diff --git a/lms/djangoapps/commerce/api/v1/models.py b/lms/djangoapps/commerce/api/v1/models.py index c8acf13168..0641b85ef6 100644 --- a/lms/djangoapps/commerce/api/v1/models.py +++ b/lms/djangoapps/commerce/api/v1/models.py @@ -1,11 +1,11 @@ """ API v1 models. """ from itertools import groupby -import logging +import logging from django.db import transaction from opaque_keys import InvalidKeyError from opaque_keys.edx.keys import CourseKey - +from openedx.core.djangoapps.content.course_overviews.models import CourseOverview from course_modes.models import CourseMode log = logging.getLogger(__name__) @@ -22,6 +22,19 @@ class Course(object): self.modes = list(modes) self._deleted_modes = [] + @property + def name(self): + """ Return course name. """ + course_id = CourseKey.from_string(unicode(self.id)) # pylint: disable=invalid-name + + try: + return CourseOverview.get_from_id(course_id).display_name + except CourseOverview.DoesNotExist: + # NOTE (CCB): Ideally, the course modes table should only contain data for courses that exist in + # modulestore. If that is not the case, say for local development/testing, carry on without failure. + log.warning('Failed to retrieve CourseOverview for [%s]. Using empty course name.', course_id) + return None + def get_mode_display_name(self, mode): """ Returns display name for the given mode. """ slug = mode.mode_slug.strip().lower() diff --git a/lms/djangoapps/commerce/api/v1/serializers.py b/lms/djangoapps/commerce/api/v1/serializers.py index 2970eaf10d..37e8890ab1 100644 --- a/lms/djangoapps/commerce/api/v1/serializers.py +++ b/lms/djangoapps/commerce/api/v1/serializers.py @@ -25,6 +25,7 @@ class CourseModeSerializer(serializers.ModelSerializer): class CourseSerializer(serializers.Serializer): """ Course serializer. """ id = serializers.CharField() # pylint: disable=invalid-name + name = serializers.CharField(read_only=True) modes = CourseModeSerializer(many=True, allow_add_remove=True) def restore_object(self, attrs, instance=None): diff --git a/lms/djangoapps/commerce/api/v1/tests/test_views.py b/lms/djangoapps/commerce/api/v1/tests/test_views.py index 7419fe8362..2ef0602b96 100644 --- a/lms/djangoapps/commerce/api/v1/tests/test_views.py +++ b/lms/djangoapps/commerce/api/v1/tests/test_views.py @@ -47,6 +47,17 @@ class CourseApiViewTestMixin(object): u'expires': expires, } + @classmethod + def _serialize_course(cls, course, modes=None): + """ Serializes a course to a Python dict. """ + modes = modes or [] + + return { + u'id': unicode(course.id), + u'name': unicode(course.display_name), + u'modes': [cls._serialize_course_mode(mode) for mode in modes] + } + class CourseListViewTests(CourseApiViewTestMixin, ModuleStoreTestCase): """ Tests for CourseListView. """ @@ -66,12 +77,7 @@ class CourseListViewTests(CourseApiViewTestMixin, ModuleStoreTestCase): self.assertEqual(response.status_code, 200) actual = json.loads(response.content) - expected = [ - { - u'id': unicode(self.course.id), - u'modes': [self._serialize_course_mode(self.course_mode)] - } - ] + expected = [self._serialize_course(self.course, [self.course_mode])] self.assertListEqual(actual, expected) @@ -104,10 +110,7 @@ class CourseRetrieveUpdateViewTests(CourseApiViewTestMixin, ModuleStoreTestCase) self.assertEqual(response.status_code, 200) actual = json.loads(response.content) - expected = { - u'id': unicode(self.course.id), - u'modes': [self._serialize_course_mode(self.course_mode)] - } + expected = self._serialize_course(self.course, [self.course_mode]) self.assertEqual(actual, expected) def test_retrieve_invalid_course(self): @@ -128,10 +131,8 @@ class CourseRetrieveUpdateViewTests(CourseApiViewTestMixin, ModuleStoreTestCase) sku=u'ABC123', expiration_datetime=expiration_datetime ) - expected = { - u'id': unicode(self.course.id), - u'modes': [self._serialize_course_mode(expected_course_mode)] - } + expected = self._serialize_course(self.course, [expected_course_mode]) + response = self.client.put(self.path, json.dumps(expected), content_type=JSON_CONTENT_TYPE) self.assertEqual(response.status_code, 200) @@ -145,11 +146,8 @@ class CourseRetrieveUpdateViewTests(CourseApiViewTestMixin, ModuleStoreTestCase) self.user.user_permissions.add(permission) course_id = unicode(self.course.id) - expected = { - u'id': course_id, - u'modes': [self._serialize_course_mode( - CourseMode(mode_slug=u'credit', min_price=500, currency=u'USD', sku=u'ABC123')), ] - } + expected_course_mode = CourseMode(mode_slug=u'credit', min_price=500, currency=u'USD', sku=u'ABC123') + expected = self._serialize_course(self.course, [expected_course_mode]) path = reverse('commerce_api:v1:courses:retrieve_update', args=[course_id]) response = self.client.put(path, json.dumps(expected), content_type=JSON_CONTENT_TYPE) self.assertEqual(response.status_code, 200) @@ -190,19 +188,14 @@ class CourseRetrieveUpdateViewTests(CourseApiViewTestMixin, ModuleStoreTestCase) def assert_can_create_course(self, **request_kwargs): """ Verify a course can be created by the view. """ course = CourseFactory.create() - course_id = unicode(course.id) - expected = { - u'id': course_id, - u'modes': [ - self._serialize_course_mode( - CourseMode(mode_slug=u'verified', min_price=150, currency=u'USD', sku=u'ABC123')), - self._serialize_course_mode( - CourseMode(mode_slug=u'honor', min_price=0, currency=u'USD', sku=u'DEADBEEF')), - ] - } - path = reverse('commerce_api:v1:courses:retrieve_update', args=[course_id]) + expected_modes = [CourseMode(mode_slug=u'verified', min_price=150, currency=u'USD', sku=u'ABC123'), + CourseMode(mode_slug=u'honor', min_price=0, currency=u'USD', sku=u'DEADBEEF')] + expected = self._serialize_course(course, expected_modes) + path = reverse('commerce_api:v1:courses:retrieve_update', args=[unicode(course.id)]) + response = self.client.put(path, json.dumps(expected), content_type=JSON_CONTENT_TYPE, **request_kwargs) self.assertEqual(response.status_code, 201) + actual = json.loads(response.content) self.assertEqual(actual, expected)