Commit cb3140e9 by tlindaliu

Merge pull request #8782 from edx/lliu/courseware-access

Add courseware_access field to mobile API
parents 9d574b4e 5aeccd52
...@@ -4,8 +4,12 @@ Serializer for user API ...@@ -4,8 +4,12 @@ Serializer for user API
from rest_framework import serializers from rest_framework import serializers
from rest_framework.reverse import reverse from rest_framework.reverse import reverse
from django.template import defaultfilters
from courseware.access import has_access
from student.models import CourseEnrollment, User from student.models import CourseEnrollment, User
from certificates.models import certificate_status_for_student, CertificateStatuses from certificates.models import certificate_status_for_student, CertificateStatuses
from xmodule.course_module import DEFAULT_START_DATE
class CourseOverviewField(serializers.RelatedField): class CourseOverviewField(serializers.RelatedField):
...@@ -35,12 +39,24 @@ class CourseOverviewField(serializers.RelatedField): ...@@ -35,12 +39,24 @@ class CourseOverviewField(serializers.RelatedField):
course_updates_url = None course_updates_url = None
course_handouts_url = None course_handouts_url = 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
return { return {
"id": course_id, "id": course_id,
"name": course_overview.display_name, "name": course_overview.display_name,
"number": course_overview.display_number_with_default, "number": course_overview.display_number_with_default,
"org": course_overview.display_org_with_default, "org": course_overview.display_org_with_default,
"start": course_overview.start, "start": course_overview.start,
"start_display": start_display,
"start_type": start_type,
"end": course_overview.end, "end": course_overview.end,
"course_image": course_overview.course_image_url, "course_image": course_overview.course_image_url,
"social_urls": { "social_urls": {
...@@ -53,6 +69,7 @@ class CourseOverviewField(serializers.RelatedField): ...@@ -53,6 +69,7 @@ class CourseOverviewField(serializers.RelatedField):
"course_updates": course_updates_url, "course_updates": course_updates_url,
"course_handouts": course_handouts_url, "course_handouts": course_handouts_url,
"subscription_id": course_overview.clean_id(padding_char='_'), "subscription_id": course_overview.clean_id(padding_char='_'),
"courseware_access": has_access(request.user, 'load_mobile', course_overview).to_json() if request else None
} }
......
...@@ -2,12 +2,28 @@ ...@@ -2,12 +2,28 @@
Tests for users API Tests for users API
""" """
import datetime import datetime
import ddt
from mock import patch
import pytz
from django.conf import settings
from django.utils import timezone from django.utils import timezone
from django.template import defaultfilters
from xmodule.modulestore.tests.factories import ItemFactory, CourseFactory
from student.models import CourseEnrollment
from certificates.models import CertificateStatuses from certificates.models import CertificateStatuses
from certificates.tests.factories import GeneratedCertificateFactory from certificates.tests.factories import GeneratedCertificateFactory
from courseware.access_response import (
MilestoneError,
StartDateError,
VisibilityError,
)
from student.models import CourseEnrollment
from util.milestones_helpers import (
set_prerequisite_courses,
seed_milestone_relationship_types,
)
from xmodule.course_module import DEFAULT_START_DATE
from xmodule.modulestore.tests.factories import ItemFactory, CourseFactory
from .. import errors from .. import errors
from ..testutils import MobileAPITestCase, MobileAuthTestMixin, MobileAuthUserTestMixin, MobileCourseAccessTestMixin from ..testutils import MobileAPITestCase, MobileAuthTestMixin, MobileAuthUserTestMixin, MobileCourseAccessTestMixin
...@@ -43,6 +59,7 @@ class TestUserInfoApi(MobileAPITestCase, MobileAuthTestMixin): ...@@ -43,6 +59,7 @@ class TestUserInfoApi(MobileAPITestCase, MobileAuthTestMixin):
self.assertTrue(self.username in response['location']) self.assertTrue(self.username in response['location'])
@ddt.ddt
class TestUserEnrollmentApi(MobileAPITestCase, MobileAuthUserTestMixin, MobileCourseAccessTestMixin): class TestUserEnrollmentApi(MobileAPITestCase, MobileAuthUserTestMixin, MobileCourseAccessTestMixin):
""" """
Tests for /api/mobile/v0.5/users/<user_name>/course_enrollments/ Tests for /api/mobile/v0.5/users/<user_name>/course_enrollments/
...@@ -50,6 +67,9 @@ class TestUserEnrollmentApi(MobileAPITestCase, MobileAuthUserTestMixin, MobileCo ...@@ -50,6 +67,9 @@ class TestUserEnrollmentApi(MobileAPITestCase, MobileAuthUserTestMixin, MobileCo
REVERSE_INFO = {'name': 'courseenrollment-detail', 'params': ['username']} REVERSE_INFO = {'name': 'courseenrollment-detail', 'params': ['username']}
ALLOW_ACCESS_TO_UNRELEASED_COURSE = True ALLOW_ACCESS_TO_UNRELEASED_COURSE = True
ALLOW_ACCESS_TO_MILESTONE_COURSE = True ALLOW_ACCESS_TO_MILESTONE_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"
def verify_success(self, response): def verify_success(self, response):
super(TestUserEnrollmentApi, self).verify_success(response) super(TestUserEnrollmentApi, self).verify_success(response)
...@@ -73,18 +93,78 @@ class TestUserEnrollmentApi(MobileAPITestCase, MobileAuthUserTestMixin, MobileCo ...@@ -73,18 +93,78 @@ class TestUserEnrollmentApi(MobileAPITestCase, MobileAuthUserTestMixin, MobileCo
num_courses = 3 num_courses = 3
courses = [] courses = []
for course_num in range(num_courses): for course_index in range(num_courses):
courses.append(CourseFactory.create(mobile_available=True)) courses.append(CourseFactory.create(mobile_available=True))
self.enroll(courses[course_num].id) self.enroll(courses[course_index].id)
# verify courses are returned in the order of enrollment, with most recently enrolled first. # verify courses are returned in the order of enrollment, with most recently enrolled first.
response = self.api_response() response = self.api_response()
for course_num in range(num_courses): for course_index in range(num_courses):
self.assertEqual( self.assertEqual(
response.data[course_num]['course']['id'], # pylint: disable=no-member response.data[course_index]['course']['id'], # pylint: disable=no-member
unicode(courses[num_courses - course_num - 1].id) unicode(courses[num_courses - course_index - 1].id)
) )
@patch.dict(
settings.FEATURES, {'ENABLE_PREREQUISITE_COURSES': True, 'MILESTONES_APP': True, 'DISABLE_START_DATES': False}
)
def test_courseware_access(self):
seed_milestone_relationship_types()
self.login()
course_with_prereq = CourseFactory.create(start=self.LAST_WEEK, mobile_available=True)
prerequisite_course = CourseFactory.create()
set_prerequisite_courses(course_with_prereq.id, [unicode(prerequisite_course.id)])
# Create list of courses with various expected courseware_access responses and corresponding expected codes
courses = [
course_with_prereq,
CourseFactory.create(start=self.NEXT_WEEK, mobile_available=True),
CourseFactory.create(visible_to_staff_only=True, mobile_available=True),
CourseFactory.create(start=self.LAST_WEEK, mobile_available=True, visible_to_staff_only=False),
]
expected_error_codes = [
MilestoneError().error_code, # 'unfulfilled_milestones'
StartDateError(self.NEXT_WEEK).error_code, # 'course_not_started'
VisibilityError().error_code, # 'not_visible_to_user'
None,
]
# Enroll in all the courses
for course in courses:
self.enroll(course.id)
# Verify courses have the correct response through error code. Last enrolled course is first course in response
response = self.api_response()
for course_index in range(len(courses)):
result = response.data[course_index]['course']['courseware_access'] # pylint: disable=no-member
self.assertEqual(result['error_code'], expected_error_codes[::-1][course_index])
if result['error_code'] is not None:
self.assertFalse(result['has_access'])
@ddt.data(
(NEXT_WEEK, ADVERTISED_START, ADVERTISED_START, "string"),
(NEXT_WEEK, None, defaultfilters.date(NEXT_WEEK, "DATE_FORMAT"), "timestamp"),
(DEFAULT_START_DATE, ADVERTISED_START, ADVERTISED_START, "string"),
(DEFAULT_START_DATE, None, None, "empty")
)
@ddt.unpack
@patch.dict('django.conf.settings.FEATURES', {'DISABLE_START_DATES': False})
def test_start_type_and_display(self, start, advertised_start, expected_display, expected_type):
"""
Tests that the correct start_type and start_display are returned in the
case the course has not started
"""
self.login()
course = CourseFactory.create(start=start, advertised_start=advertised_start, mobile_available=True)
self.enroll(course.id)
response = self.api_response()
self.assertEqual(response.data[0]['course']['start_type'], expected_type) # pylint: disable=no-member
self.assertEqual(response.data[0]['course']['start_display'], expected_display) # pylint: disable=no-member
def test_no_certificate(self): def test_no_certificate(self):
self.login_and_enroll() self.login_and_enroll()
......
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