Commit 330120bb by Nimisha Asthagiri

MA-1712: Update Mobile API to include course_about

parent a7e21bf1
......@@ -139,14 +139,17 @@ class MobileCourseAccessTestMixin(MobileAPIMilestonesMixin):
Subclasses can override verify_success, verify_failure, and init_course_access methods.
"""
ALLOW_ACCESS_TO_UNRELEASED_COURSE = False # pylint: disable=invalid-name
ALLOW_ACCESS_TO_NON_VISIBLE_COURSE = False # pylint: disable=invalid-name
def verify_success(self, response):
"""Base implementation of verifying a successful response."""
self.assertEqual(response.status_code, 200)
def verify_failure(self, response):
def verify_failure(self, response, error_type=None):
"""Base implementation of verifying a failed response."""
self.assertEqual(response.status_code, 404)
if error_type:
self.assertEqual(response.data, error_type.to_json())
def init_course_access(self, course_id=None):
"""Base implementation of initializing the user for each test."""
......@@ -201,6 +204,8 @@ class MobileCourseAccessTestMixin(MobileAPIMilestonesMixin):
self.init_course_access()
self.course.visible_to_staff_only = True
self.store.update_item(self.course, self.user.id)
if self.ALLOW_ACCESS_TO_NON_VISIBLE_COURSE:
should_succeed = True
self._verify_response(should_succeed, VisibilityError(), role)
def _verify_response(self, should_succeed, error_type, role=None):
......@@ -216,5 +221,4 @@ class MobileCourseAccessTestMixin(MobileAPIMilestonesMixin):
if should_succeed:
self.verify_success(response)
else:
self.verify_failure(response)
self.assertEqual(response.data, error_type.to_json())
self.verify_failure(response, error_type)
......@@ -13,70 +13,83 @@ from xmodule.course_module import DEFAULT_START_DATE
class CourseOverviewField(serializers.RelatedField):
"""Custom field to wrap a CourseDescriptor object. Read-only."""
"""
Custom field to wrap a CourseOverview object. Read-only.
"""
def to_representation(self, course_overview):
course_id = unicode(course_overview.id)
request = self.context.get('request', None)
if request:
video_outline_url = reverse(
'video-summary-list',
if course_overview.advertised_start is not None:
start_type = 'string'
start_display = course_overview.advertised_start
elif course_overview.start != DEFAULT_START_DATE:
start_type = 'timestamp'
start_display = defaultfilters.date(course_overview.start, 'DATE_FORMAT')
else:
start_type = 'empty'
start_display = None
request = self.context.get('request')
return {
# identifiers
'id': course_id,
'name': course_overview.display_name,
'number': course_overview.display_number_with_default,
'org': course_overview.display_org_with_default,
# dates
'start': course_overview.start,
'start_display': start_display,
'start_type': start_type,
'end': course_overview.end,
# notification info
'subscription_id': course_overview.clean_id(padding_char='_'),
# access info
'courseware_access': has_access(
request.user,
'load_mobile',
course_overview
).to_json(),
# various URLs
'course_image': course_overview.course_image_url,
'course_about': reverse(
'about_course',
kwargs={'course_id': course_id},
request=request
)
course_updates_url = reverse(
request=request,
),
'course_updates': reverse(
'course-updates-list',
kwargs={'course_id': course_id},
request=request
)
course_handouts_url = reverse(
request=request,
),
'course_handouts': reverse(
'course-handouts-list',
kwargs={'course_id': course_id},
request=request
)
discussion_url = reverse(
request=request,
),
'discussion_url': reverse(
'discussion_course',
kwargs={'course_id': course_id},
request=request
) if course_overview.is_discussion_tab_enabled() else None
else:
video_outline_url = None
course_updates_url = None
course_handouts_url = None
discussion_url = None
request=request,
) if course_overview.is_discussion_tab_enabled() else None,
if course_overview.advertised_start is not None:
start_type = "string"
start_display = course_overview.advertised_start
elif course_overview.start != DEFAULT_START_DATE:
start_type = "timestamp"
start_display = defaultfilters.date(course_overview.start, "DATE_FORMAT")
else:
start_type = "empty"
start_display = None
'video_outline': reverse(
'video-summary-list',
kwargs={'course_id': course_id},
request=request,
),
return {
"id": course_id,
"name": course_overview.display_name,
"number": course_overview.display_number_with_default,
"org": course_overview.display_org_with_default,
"start": course_overview.start,
"start_display": start_display,
"start_type": start_type,
"end": course_overview.end,
"course_image": course_overview.course_image_url,
"social_urls": {
"facebook": course_overview.facebook_url,
# Note: The following 2 should be deprecated.
'social_urls': {
'facebook': course_overview.facebook_url,
},
"latest_updates": {
"video": None
'latest_updates': {
'video': None
},
"video_outline": video_outline_url,
"course_updates": course_updates_url,
"course_handouts": course_handouts_url,
"discussion_url": discussion_url,
"subscription_id": course_overview.clean_id(padding_char='_'),
"courseware_access": has_access(request.user, 'load_mobile', course_overview).to_json() if request else None
}
......
......@@ -9,6 +9,7 @@ import pytz
from django.conf import settings
from django.utils import timezone
from django.template import defaultfilters
from django.test import RequestFactory
from certificates.models import CertificateStatuses
from certificates.tests.factories import GeneratedCertificateFactory
......@@ -24,11 +25,11 @@ from util.milestones_helpers import (
)
from xmodule.course_module import DEFAULT_START_DATE
from xmodule.modulestore.tests.factories import ItemFactory, CourseFactory
from util.testing import UrlResetMixin
from .. import errors
from ..testutils import MobileAPITestCase, MobileAuthTestMixin, MobileAuthUserTestMixin, MobileCourseAccessTestMixin
from .serializers import CourseEnrollmentSerializer
from util.testing import UrlResetMixin
class TestUserDetailApi(MobileAPITestCase, MobileAuthUserTestMixin):
......@@ -61,13 +62,14 @@ class TestUserInfoApi(MobileAPITestCase, MobileAuthTestMixin):
@ddt.ddt
class TestUserEnrollmentApi(UrlResetMixin, MobileAPITestCase, MobileAuthUserTestMixin):
class TestUserEnrollmentApi(UrlResetMixin, MobileAPITestCase, MobileAuthUserTestMixin, MobileCourseAccessTestMixin):
"""
Tests for /api/mobile/v0.5/users/<user_name>/course_enrollments/
"""
REVERSE_INFO = {'name': 'courseenrollment-detail', 'params': ['username']}
ALLOW_ACCESS_TO_UNRELEASED_COURSE = True
ALLOW_ACCESS_TO_MILESTONE_COURSE = True
ALLOW_ACCESS_TO_NON_VISIBLE_COURSE = True
NEXT_WEEK = datetime.datetime.now(pytz.UTC) + datetime.timedelta(days=7)
LAST_WEEK = datetime.datetime.now(pytz.UTC) - datetime.timedelta(days=7)
ADVERTISED_START = "Spring 2016"
......@@ -85,13 +87,15 @@ class TestUserEnrollmentApi(UrlResetMixin, MobileAPITestCase, MobileAuthUserTest
self.assertEqual(len(courses), 1)
found_course = courses[0]['course']
self.assertTrue('video_outline' in found_course)
self.assertTrue('course_handouts' in found_course)
self.assertIn('courses/{}/about'.format(self.course.id), found_course['course_about'])
self.assertIn('course_info/{}/updates'.format(self.course.id), found_course['course_updates'])
self.assertIn('course_info/{}/handouts'.format(self.course.id), found_course['course_handouts'])
self.assertIn('video_outlines/courses/{}'.format(self.course.id), found_course['video_outline'])
self.assertEqual(found_course['id'], unicode(self.course.id))
self.assertEqual(courses[0]['mode'], 'honor')
self.assertEqual(courses[0]['course']['subscription_id'], self.course.clean_id(padding_char='_'))
def verify_failure(self, response):
def verify_failure(self, response, error_type=None):
self.assertEqual(response.status_code, 200)
courses = response.data
self.assertEqual(len(courses), 0)
......@@ -380,22 +384,29 @@ class TestCourseEnrollmentSerializer(MobileAPITestCase):
"""
Test the course enrollment serializer
"""
def test_success(self):
def setUp(self):
super(TestCourseEnrollmentSerializer, self).setUp()
self.login_and_enroll()
self.request = RequestFactory().get('/')
self.request.user = self.user
serialized = CourseEnrollmentSerializer(CourseEnrollment.enrollments_for_user(self.user)[0]).data
self.assertEqual(serialized['course']['video_outline'], None)
def test_success(self):
serialized = CourseEnrollmentSerializer(
CourseEnrollment.enrollments_for_user(self.user)[0],
context={'request': self.request},
).data
self.assertEqual(serialized['course']['name'], self.course.display_name)
self.assertEqual(serialized['course']['number'], self.course.id.course)
self.assertEqual(serialized['course']['org'], self.course.id.org)
def test_with_display_overrides(self):
self.login_and_enroll()
self.course.display_coursenumber = "overridden_number"
self.course.display_organization = "overridden_org"
self.store.update_item(self.course, self.user.id)
serialized = CourseEnrollmentSerializer(CourseEnrollment.enrollments_for_user(self.user)[0]).data
serialized = CourseEnrollmentSerializer(
CourseEnrollment.enrollments_for_user(self.user)[0],
context={'request': self.request},
).data
self.assertEqual(serialized['course']['number'], self.course.display_coursenumber)
self.assertEqual(serialized['course']['org'], self.course.display_organization)
......@@ -225,9 +225,15 @@ class UserCourseEnrollmentsList(generics.ListAPIView):
course.
* course: A collection of the following data about the course.
* courseware_access: A JSON representation with access information for the course,
including any access errors.
* course_about: The URL to the course about page.
* course_handouts: The URI to get data for course handouts.
* course_image: The path to the course image.
* course_updates: The URI to get data for course updates.
* discussion_url: The URI to access data for course discussions if
it is enabled, otherwise null.
* end: The end date of the course.
* id: The unique ID of the course.
* latest_updates: Reserved for future use.
......@@ -235,12 +241,15 @@ class UserCourseEnrollmentsList(generics.ListAPIView):
* number: The course number.
* org: The organization that created the course.
* start: The date and time when the course starts.
* start_display:
If start_type is a string, then the advertised_start date for the course.
If start_type is a timestamp, then a formatted date for the start of the course.
If start_type is empty, then the value is None and it indicates that the course has not yet started.
* start_type: One of either "string", "timestamp", or "empty"
* subscription_id: A unique "clean" (alphanumeric with '_') ID of
the course.
* video_outline: The URI to get the list of all videos that the user
can access in the course.
* discussion_url: The URI to access data for course discussions if
it is enabled, otherwise null.
* created: The date the course was created.
* is_active: Whether the course is currently active. Possible values
......
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