Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
115ab3c1
Commit
115ab3c1
authored
Nov 25, 2015
by
Nimisha Asthagiri
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #10720 from edx/mobile/course-about-url-MA-1712
MA-1712: Update Mobile API to include course_about
parents
1354a0c3
330120bb
Show whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
103 additions
and
66 deletions
+103
-66
lms/djangoapps/mobile_api/testutils.py
+7
-3
lms/djangoapps/mobile_api/users/serializers.py
+63
-50
lms/djangoapps/mobile_api/users/tests.py
+22
-11
lms/djangoapps/mobile_api/users/views.py
+11
-2
No files found.
lms/djangoapps/mobile_api/testutils.py
View file @
115ab3c1
...
@@ -139,14 +139,17 @@ class MobileCourseAccessTestMixin(MobileAPIMilestonesMixin):
...
@@ -139,14 +139,17 @@ class MobileCourseAccessTestMixin(MobileAPIMilestonesMixin):
Subclasses can override verify_success, verify_failure, and init_course_access methods.
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_UNRELEASED_COURSE
=
False
# pylint: disable=invalid-name
ALLOW_ACCESS_TO_NON_VISIBLE_COURSE
=
False
# pylint: disable=invalid-name
def
verify_success
(
self
,
response
):
def
verify_success
(
self
,
response
):
"""Base implementation of verifying a successful response."""
"""Base implementation of verifying a successful response."""
self
.
assertEqual
(
response
.
status_code
,
200
)
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."""
"""Base implementation of verifying a failed response."""
self
.
assertEqual
(
response
.
status_code
,
404
)
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
):
def
init_course_access
(
self
,
course_id
=
None
):
"""Base implementation of initializing the user for each test."""
"""Base implementation of initializing the user for each test."""
...
@@ -201,6 +204,8 @@ class MobileCourseAccessTestMixin(MobileAPIMilestonesMixin):
...
@@ -201,6 +204,8 @@ class MobileCourseAccessTestMixin(MobileAPIMilestonesMixin):
self
.
init_course_access
()
self
.
init_course_access
()
self
.
course
.
visible_to_staff_only
=
True
self
.
course
.
visible_to_staff_only
=
True
self
.
store
.
update_item
(
self
.
course
,
self
.
user
.
id
)
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
)
self
.
_verify_response
(
should_succeed
,
VisibilityError
(),
role
)
def
_verify_response
(
self
,
should_succeed
,
error_type
,
role
=
None
):
def
_verify_response
(
self
,
should_succeed
,
error_type
,
role
=
None
):
...
@@ -216,5 +221,4 @@ class MobileCourseAccessTestMixin(MobileAPIMilestonesMixin):
...
@@ -216,5 +221,4 @@ class MobileCourseAccessTestMixin(MobileAPIMilestonesMixin):
if
should_succeed
:
if
should_succeed
:
self
.
verify_success
(
response
)
self
.
verify_success
(
response
)
else
:
else
:
self
.
verify_failure
(
response
)
self
.
verify_failure
(
response
,
error_type
)
self
.
assertEqual
(
response
.
data
,
error_type
.
to_json
())
lms/djangoapps/mobile_api/users/serializers.py
View file @
115ab3c1
...
@@ -13,70 +13,83 @@ from xmodule.course_module import DEFAULT_START_DATE
...
@@ -13,70 +13,83 @@ from xmodule.course_module import DEFAULT_START_DATE
class
CourseOverviewField
(
serializers
.
RelatedField
):
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
):
def
to_representation
(
self
,
course_overview
):
course_id
=
unicode
(
course_overview
.
id
)
course_id
=
unicode
(
course_overview
.
id
)
request
=
self
.
context
.
get
(
'request'
,
None
)
if
request
:
if
course_overview
.
advertised_start
is
not
None
:
video_outline_url
=
reverse
(
start_type
=
'string'
'video-summary-list'
,
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
},
kwargs
=
{
'course_id'
:
course_id
},
request
=
request
request
=
request
,
)
)
,
course_updates_url
=
reverse
(
'course_updates'
:
reverse
(
'course-updates-list'
,
'course-updates-list'
,
kwargs
=
{
'course_id'
:
course_id
},
kwargs
=
{
'course_id'
:
course_id
},
request
=
request
request
=
request
,
)
)
,
course_handouts_url
=
reverse
(
'course_handouts'
:
reverse
(
'course-handouts-list'
,
'course-handouts-list'
,
kwargs
=
{
'course_id'
:
course_id
},
kwargs
=
{
'course_id'
:
course_id
},
request
=
request
request
=
request
,
)
)
,
discussion_url
=
reverse
(
'discussion_url'
:
reverse
(
'discussion_course'
,
'discussion_course'
,
kwargs
=
{
'course_id'
:
course_id
},
kwargs
=
{
'course_id'
:
course_id
},
request
=
request
request
=
request
,
)
if
course_overview
.
is_discussion_tab_enabled
()
else
None
)
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
if
course_overview
.
advertised_start
is
not
None
:
'video_outline'
:
reverse
(
start_type
=
"string"
'video-summary-list'
,
start_display
=
course_overview
.
advertised_start
kwargs
=
{
'course_id'
:
course_id
},
elif
course_overview
.
start
!=
DEFAULT_START_DATE
:
request
=
request
,
start_type
=
"timestamp"
),
start_display
=
defaultfilters
.
date
(
course_overview
.
start
,
"DATE_FORMAT"
)
else
:
start_type
=
"empty"
start_display
=
None
return
{
# Note: The following 2 should be deprecated.
"id"
:
course_id
,
'social_urls'
:
{
"name"
:
course_overview
.
display_name
,
'facebook'
:
course_overview
.
facebook_url
,
"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
,
},
},
"latest_updates"
:
{
'latest_updates'
:
{
"video"
:
None
'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
}
}
...
...
lms/djangoapps/mobile_api/users/tests.py
View file @
115ab3c1
...
@@ -9,6 +9,7 @@ import pytz
...
@@ -9,6 +9,7 @@ import pytz
from
django.conf
import
settings
from
django.conf
import
settings
from
django.utils
import
timezone
from
django.utils
import
timezone
from
django.template
import
defaultfilters
from
django.template
import
defaultfilters
from
django.test
import
RequestFactory
from
certificates.models
import
CertificateStatuses
from
certificates.models
import
CertificateStatuses
from
certificates.tests.factories
import
GeneratedCertificateFactory
from
certificates.tests.factories
import
GeneratedCertificateFactory
...
@@ -24,11 +25,11 @@ from util.milestones_helpers import (
...
@@ -24,11 +25,11 @@ from util.milestones_helpers import (
)
)
from
xmodule.course_module
import
DEFAULT_START_DATE
from
xmodule.course_module
import
DEFAULT_START_DATE
from
xmodule.modulestore.tests.factories
import
ItemFactory
,
CourseFactory
from
xmodule.modulestore.tests.factories
import
ItemFactory
,
CourseFactory
from
util.testing
import
UrlResetMixin
from
..
import
errors
from
..
import
errors
from
..testutils
import
MobileAPITestCase
,
MobileAuthTestMixin
,
MobileAuthUserTestMixin
,
MobileCourseAccessTestMixin
from
..testutils
import
MobileAPITestCase
,
MobileAuthTestMixin
,
MobileAuthUserTestMixin
,
MobileCourseAccessTestMixin
from
.serializers
import
CourseEnrollmentSerializer
from
.serializers
import
CourseEnrollmentSerializer
from
util.testing
import
UrlResetMixin
class
TestUserDetailApi
(
MobileAPITestCase
,
MobileAuthUserTestMixin
):
class
TestUserDetailApi
(
MobileAPITestCase
,
MobileAuthUserTestMixin
):
...
@@ -61,13 +62,14 @@ class TestUserInfoApi(MobileAPITestCase, MobileAuthTestMixin):
...
@@ -61,13 +62,14 @@ class TestUserInfoApi(MobileAPITestCase, MobileAuthTestMixin):
@ddt.ddt
@ddt.ddt
class
TestUserEnrollmentApi
(
UrlResetMixin
,
MobileAPITestCase
,
MobileAuthUserTestMixin
):
class
TestUserEnrollmentApi
(
UrlResetMixin
,
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/
"""
"""
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
ALLOW_ACCESS_TO_NON_VISIBLE_COURSE
=
True
NEXT_WEEK
=
datetime
.
datetime
.
now
(
pytz
.
UTC
)
+
datetime
.
timedelta
(
days
=
7
)
NEXT_WEEK
=
datetime
.
datetime
.
now
(
pytz
.
UTC
)
+
datetime
.
timedelta
(
days
=
7
)
LAST_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"
ADVERTISED_START
=
"Spring 2016"
...
@@ -85,13 +87,15 @@ class TestUserEnrollmentApi(UrlResetMixin, MobileAPITestCase, MobileAuthUserTest
...
@@ -85,13 +87,15 @@ class TestUserEnrollmentApi(UrlResetMixin, MobileAPITestCase, MobileAuthUserTest
self
.
assertEqual
(
len
(
courses
),
1
)
self
.
assertEqual
(
len
(
courses
),
1
)
found_course
=
courses
[
0
][
'course'
]
found_course
=
courses
[
0
][
'course'
]
self
.
assertTrue
(
'video_outline'
in
found_course
)
self
.
assertIn
(
'courses/{}/about'
.
format
(
self
.
course
.
id
),
found_course
[
'course_about'
])
self
.
assertTrue
(
'course_handouts'
in
found_course
)
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
(
found_course
[
'id'
],
unicode
(
self
.
course
.
id
))
self
.
assertEqual
(
courses
[
0
][
'mode'
],
'honor'
)
self
.
assertEqual
(
courses
[
0
][
'mode'
],
'honor'
)
self
.
assertEqual
(
courses
[
0
][
'course'
][
'subscription_id'
],
self
.
course
.
clean_id
(
padding_char
=
'_'
))
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
)
self
.
assertEqual
(
response
.
status_code
,
200
)
courses
=
response
.
data
courses
=
response
.
data
self
.
assertEqual
(
len
(
courses
),
0
)
self
.
assertEqual
(
len
(
courses
),
0
)
...
@@ -380,22 +384,29 @@ class TestCourseEnrollmentSerializer(MobileAPITestCase):
...
@@ -380,22 +384,29 @@ class TestCourseEnrollmentSerializer(MobileAPITestCase):
"""
"""
Test the course enrollment serializer
Test the course enrollment serializer
"""
"""
def
test_success
(
self
):
def
setUp
(
self
):
super
(
TestCourseEnrollmentSerializer
,
self
)
.
setUp
()
self
.
login_and_enroll
()
self
.
login_and_enroll
()
self
.
request
=
RequestFactory
()
.
get
(
'/'
)
self
.
request
.
user
=
self
.
user
serialized
=
CourseEnrollmentSerializer
(
CourseEnrollment
.
enrollments_for_user
(
self
.
user
)[
0
])
.
data
def
test_success
(
self
):
self
.
assertEqual
(
serialized
[
'course'
][
'video_outline'
],
None
)
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'
][
'name'
],
self
.
course
.
display_name
)
self
.
assertEqual
(
serialized
[
'course'
][
'number'
],
self
.
course
.
id
.
course
)
self
.
assertEqual
(
serialized
[
'course'
][
'number'
],
self
.
course
.
id
.
course
)
self
.
assertEqual
(
serialized
[
'course'
][
'org'
],
self
.
course
.
id
.
org
)
self
.
assertEqual
(
serialized
[
'course'
][
'org'
],
self
.
course
.
id
.
org
)
def
test_with_display_overrides
(
self
):
def
test_with_display_overrides
(
self
):
self
.
login_and_enroll
()
self
.
course
.
display_coursenumber
=
"overridden_number"
self
.
course
.
display_coursenumber
=
"overridden_number"
self
.
course
.
display_organization
=
"overridden_org"
self
.
course
.
display_organization
=
"overridden_org"
self
.
store
.
update_item
(
self
.
course
,
self
.
user
.
id
)
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'
][
'number'
],
self
.
course
.
display_coursenumber
)
self
.
assertEqual
(
serialized
[
'course'
][
'org'
],
self
.
course
.
display_organization
)
self
.
assertEqual
(
serialized
[
'course'
][
'org'
],
self
.
course
.
display_organization
)
lms/djangoapps/mobile_api/users/views.py
View file @
115ab3c1
...
@@ -225,9 +225,15 @@ class UserCourseEnrollmentsList(generics.ListAPIView):
...
@@ -225,9 +225,15 @@ class UserCourseEnrollmentsList(generics.ListAPIView):
course.
course.
* course: A collection of the following data about the 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_handouts: The URI to get data for course handouts.
* course_image: The path to the course image.
* course_image: The path to the course image.
* course_updates: The URI to get data for course updates.
* 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.
* end: The end date of the course.
* id: The unique ID of the course.
* id: The unique ID of the course.
* latest_updates: Reserved for future use.
* latest_updates: Reserved for future use.
...
@@ -235,12 +241,15 @@ class UserCourseEnrollmentsList(generics.ListAPIView):
...
@@ -235,12 +241,15 @@ class UserCourseEnrollmentsList(generics.ListAPIView):
* number: The course number.
* number: The course number.
* org: The organization that created the course.
* org: The organization that created the course.
* start: The date and time when the course starts.
* 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
* subscription_id: A unique "clean" (alphanumeric with '_') ID of
the course.
the course.
* video_outline: The URI to get the list of all videos that the user
* video_outline: The URI to get the list of all videos that the user
can access in the course.
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.
* created: The date the course was created.
* is_active: Whether the course is currently active. Possible values
* is_active: Whether the course is currently active. Possible values
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment