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
691fed7a
Commit
691fed7a
authored
Oct 02, 2015
by
Nimisha Asthagiri
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fixup! courses api - new endpoint that takes course_id
parent
6b2e00e9
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
283 additions
and
58 deletions
+283
-58
lms/djangoapps/course_api/blocks/api.py
+48
-0
lms/djangoapps/course_api/blocks/tests/test_api.py
+32
-0
lms/djangoapps/course_api/blocks/tests/test_views.py
+50
-15
lms/djangoapps/course_api/blocks/transformers/student_view.py
+2
-2
lms/djangoapps/course_api/blocks/urls.py
+12
-4
lms/djangoapps/course_api/blocks/views.py
+135
-33
lms/djangoapps/course_api/urls.py
+2
-2
lms/djangoapps/course_api/views.py
+1
-1
lms/urls.py
+1
-1
No files found.
lms/djangoapps/course_api/blocks/api.py
View file @
691fed7a
"""
TODO
"""
from
transformers.blocks_api
import
BlocksAPITransformer
from
.serializers
import
BlockSerializer
,
BlockDictSerializer
from
lms.djangoapps.course_blocks.api
import
get_course_blocks
,
LMS_COURSE_TRANSFORMERS
def
get_blocks
(
request
,
usage_key
,
user
=
None
,
depth
=
None
,
nav_depth
=
None
,
requested_fields
=
None
,
block_counts
=
None
,
student_view_data
=
None
,
return_type
=
'dict'
,
):
# TODO support user=None by returning all blocks, not just user-specific ones
if
user
is
None
:
raise
NotImplementedError
# transform blocks
blocks_api_transformer
=
BlocksAPITransformer
(
block_counts
,
student_view_data
,
depth
,
nav_depth
)
blocks
=
get_course_blocks
(
user
,
usage_key
,
transformers
=
LMS_COURSE_TRANSFORMERS
+
[
blocks_api_transformer
],
)
# serialize
serializer_context
=
{
'request'
:
request
,
'block_structure'
:
blocks
,
'requested_fields'
:
requested_fields
or
[],
}
if
return_type
==
'dict'
:
serializer
=
BlockDictSerializer
(
blocks
,
context
=
serializer_context
,
many
=
False
)
else
:
serializer
=
BlockSerializer
(
blocks
,
context
=
serializer_context
,
many
=
True
)
# return serialized data
return
serializer
.
data
lms/djangoapps/course_api/blocks/tests/test_api.py
0 → 100644
View file @
691fed7a
"""
Tests for Blocks api.py
"""
from
django.test.client
import
RequestFactory
from
student.tests.factories
import
UserFactory
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
from
xmodule.modulestore.tests.factories
import
SampleCourseFactory
from
..api
import
get_blocks
class
TestGetBlocks
(
ModuleStoreTestCase
):
"""
Tests for the get_block_list method.
"""
def
setUp
(
self
):
super
(
TestGetBlocks
,
self
)
.
setUp
()
self
.
course
=
SampleCourseFactory
.
create
()
self
.
user
=
UserFactory
.
create
()
self
.
request
=
RequestFactory
()
.
get
(
"/dummy"
)
self
.
request
.
user
=
self
.
user
def
test_basic
(
self
):
blocks
=
get_blocks
(
self
.
request
,
self
.
course
.
location
,
self
.
user
)
self
.
assertEquals
(
blocks
[
'root'
],
unicode
(
self
.
course
.
location
))
# add 1 for the orphaned course about block
self
.
assertEquals
(
len
(
blocks
[
'blocks'
])
+
1
,
len
(
self
.
store
.
get_items
(
self
.
course
.
id
)))
def
test_no_user
(
self
):
with
self
.
assertRaises
(
NotImplementedError
):
get_blocks
(
self
.
request
,
self
.
course
.
location
)
lms/djangoapps/course_api/blocks/tests/test_views.py
View file @
691fed7a
"""
Tests for
Course Blocks v
iews
Tests for
Blocks V
iews
"""
from
django.core.urlresolvers
import
reverse
...
...
@@ -12,16 +12,13 @@ from xmodule.modulestore.tests.factories import ToyCourseFactory
from
.test_utils
import
deserialize_usage_key
class
Test
CourseBlocksView
(
SharedModuleStoreTestCase
):
class
Test
BlocksViewMixin
(
object
):
"""
Test class for CourseBlocks view
Mixin class for test helpers for BlocksView related classes
"""
@classmethod
def
setUpClass
(
cls
):
super
(
TestCourseBlocksView
,
cls
)
.
setUpClass
()
def
setup_course
(
cls
):
cls
.
course_key
=
ToyCourseFactory
.
create
()
.
id
cls
.
course_usage_key
=
cls
.
store
.
make_course_usage_key
(
cls
.
course_key
)
cls
.
non_orphaned_block_usage_keys
=
set
(
unicode
(
item
.
location
)
...
...
@@ -29,14 +26,8 @@ class TestCourseBlocksView(SharedModuleStoreTestCase):
# remove all orphaned items in the course, except for the root 'course' block
if
cls
.
store
.
get_parent_location
(
item
.
location
)
or
item
.
category
==
'course'
)
cls
.
url
=
reverse
(
'course_blocks'
,
kwargs
=
{
'usage_key_string'
:
unicode
(
cls
.
course_usage_key
)}
)
def
setUp
(
self
):
super
(
TestCourseBlocksView
,
self
)
.
setUp
()
def
setup_user
(
self
):
self
.
user
=
UserFactory
.
create
()
self
.
client
.
login
(
username
=
self
.
user
.
username
,
password
=
'test'
)
...
...
@@ -74,6 +65,25 @@ class TestCourseBlocksView(SharedModuleStoreTestCase):
else
:
self
.
assertFalse
(
expression
)
class
TestBlocksView
(
TestBlocksViewMixin
,
SharedModuleStoreTestCase
):
"""
Test class for BlocksView
"""
@classmethod
def
setUpClass
(
cls
):
super
(
TestBlocksView
,
cls
)
.
setUpClass
()
cls
.
setup_course
()
cls
.
course_usage_key
=
cls
.
store
.
make_course_usage_key
(
cls
.
course_key
)
cls
.
url
=
reverse
(
'blocks_in_block_tree'
,
kwargs
=
{
'usage_key_string'
:
unicode
(
cls
.
course_usage_key
)}
)
def
setUp
(
self
):
super
(
TestBlocksView
,
self
)
.
setUp
()
self
.
setup_user
()
def
test_not_authenticated
(
self
):
self
.
client
.
logout
()
self
.
verify_response
(
401
)
...
...
@@ -85,7 +95,7 @@ class TestCourseBlocksView(SharedModuleStoreTestCase):
def
test_non_existent_course
(
self
):
usage_key
=
self
.
store
.
make_course_usage_key
(
CourseLocator
(
'non'
,
'existent'
,
'course'
))
url
=
reverse
(
'
course_blocks
'
,
'
blocks_in_block_tree
'
,
kwargs
=
{
'usage_key_string'
:
unicode
(
usage_key
)}
)
self
.
verify_response
(
403
,
url
=
url
)
...
...
@@ -153,3 +163,28 @@ class TestCourseBlocksView(SharedModuleStoreTestCase):
set
(
unicode
(
child
.
location
)
for
child
in
xblock
.
get_children
()),
set
(
block_data
[
'children'
]),
)
class
TestBlocksInCourseView
(
TestBlocksViewMixin
,
SharedModuleStoreTestCase
):
"""
Test class for BlocksInCourseView
"""
@classmethod
def
setUpClass
(
cls
):
super
(
TestBlocksInCourseView
,
cls
)
.
setUpClass
()
cls
.
setup_course
()
cls
.
url
=
reverse
(
'blocks_in_course'
)
def
setUp
(
self
):
super
(
TestBlocksInCourseView
,
self
)
.
setUp
()
self
.
setup_user
()
def
test_basic
(
self
):
response
=
self
.
verify_response
(
params
=
{
'course_id'
:
unicode
(
self
.
course_key
)})
self
.
verify_response_block_dict
(
response
)
def
test_no_course_id
(
self
):
self
.
verify_response
(
400
)
def
test_invalid_course_id
(
self
):
self
.
verify_response
(
400
,
params
=
{
'course_id'
:
'invalid_course_id'
})
lms/djangoapps/course_api/blocks/transformers/student_view.py
View file @
691fed7a
...
...
@@ -9,8 +9,8 @@ class StudentViewTransformer(BlockStructureTransformer):
STUDENT_VIEW_DATA
=
'student_view_data'
STUDENT_VIEW_MULTI_DEVICE
=
'student_view_multi_device'
def
__init__
(
self
,
requested_student_view_data
):
self
.
requested_student_view_data
=
requested_student_view_data
def
__init__
(
self
,
requested_student_view_data
=
None
):
self
.
requested_student_view_data
=
requested_student_view_data
or
[]
@classmethod
def
collect
(
cls
,
block_structure
):
...
...
lms/djangoapps/course_api/blocks/urls.py
View file @
691fed7a
...
...
@@ -3,14 +3,22 @@ Course Block API URLs
"""
from
django.conf
import
settings
from
django.conf.urls
import
patterns
,
url
from
.views
import
CourseBlocks
from
.views
import
BlocksView
,
BlocksInCourseView
urlpatterns
=
patterns
(
''
,
# This endpoint requires the usage_key for the starting block.
url
(
r"^{}"
.
format
(
settings
.
USAGE_KEY_PATTERN
),
CourseBlocks
.
as_view
(),
name
=
"course_blocks"
r'^v1/blocks/{}'
.
format
(
settings
.
USAGE_KEY_PATTERN
),
BlocksView
.
as_view
(),
name
=
"blocks_in_block_tree"
),
# This endpoint is an alternative to the above, but requires course_id as a parameter.
url
(
r'^v1/blocks/'
,
BlocksInCourseView
.
as_view
(),
name
=
"blocks_in_course"
),
)
lms/djangoapps/course_api/blocks/views.py
View file @
691fed7a
...
...
@@ -3,21 +3,22 @@ from django.http import Http404
from
rest_framework.generics
import
ListAPIView
from
rest_framework.response
import
Response
from
lms.djangoapps.course_blocks.api
import
get_course_blocks
,
LMS_COURSE_TRANSFORMERS
from
opaque_keys
import
InvalidKeyError
from
opaque_keys.edx.keys
import
CourseKey
from
openedx.core.lib.api.view_utils
import
view_auth_classes
,
DeveloperErrorViewMixin
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.exceptions
import
ItemNotFoundError
from
transformers.blocks_api
import
BlocksAPITransformer
from
.api
import
get_blocks
from
.forms
import
BlockListGetForm
from
.serializers
import
BlockSerializer
,
BlockDictSerializer
@view_auth_classes
()
class
CourseBlocks
(
DeveloperErrorViewMixin
,
ListAPIView
):
class
BlocksView
(
DeveloperErrorViewMixin
,
ListAPIView
):
"""
**Use Case**
Returns the blocks
of the cours
e according to the requesting user's access level.
Returns the blocks
within the requested block tre
e according to the requesting user's access level.
**Example requests**:
...
...
@@ -120,39 +121,140 @@ class CourseBlocks(DeveloperErrorViewMixin, ListAPIView):
course - course module object
"""
# request parameters
requested_params
=
request
.
GET
.
copy
()
#
validate
request parameters
requested_params
=
request
.
QUERY_PARAMS
.
copy
()
requested_params
.
update
({
'usage_key'
:
usage_key_string
})
params
=
BlockListGetForm
(
requested_params
,
initial
=
{
'requesting_user'
:
request
.
user
})
if
not
params
.
is_valid
():
raise
ValidationError
(
params
.
errors
)
# transform blocks
blocks_api_transformer
=
BlocksAPITransformer
(
params
.
cleaned_data
.
get
(
'block_counts'
,
[]),
params
.
cleaned_data
.
get
(
'student_view_data'
,
[]),
params
.
cleaned_data
.
get
(
'depth'
,
None
),
params
.
cleaned_data
.
get
(
'nav_depth'
,
None
),
)
try
:
blocks
=
get_course_blocks
(
params
.
cleaned_data
[
'user'
],
params
.
cleaned_data
[
'usage_key'
],
transformers
=
LMS_COURSE_TRANSFORMERS
+
[
blocks_api_transformer
],
return
Response
(
get_blocks
(
request
,
params
.
cleaned_data
[
'usage_key'
],
params
.
cleaned_data
[
'user'
],
params
.
cleaned_data
.
get
(
'depth'
,
None
),
params
.
cleaned_data
.
get
(
'nav_depth'
,
None
),
params
.
cleaned_data
[
'requested_fields'
],
params
.
cleaned_data
.
get
(
'block_counts'
,
[]),
params
.
cleaned_data
.
get
(
'student_view_data'
,
[]),
params
.
cleaned_data
[
'return_type'
]
)
)
except
ItemNotFoundError
as
exception
:
raise
Http404
(
"Course block not found: {}"
.
format
(
exception
.
message
))
# serialize
serializer_context
=
{
'request'
:
request
,
'block_structure'
:
blocks
,
'requested_fields'
:
params
.
cleaned_data
[
'requested_fields'
],
}
if
params
.
cleaned_data
[
'return_type'
]
==
'dict'
:
serializer
=
BlockDictSerializer
(
blocks
,
context
=
serializer_context
,
many
=
False
)
else
:
serializer
=
BlockSerializer
(
blocks
,
context
=
serializer_context
,
many
=
True
)
# response
return
Response
(
serializer
.
data
)
raise
Http404
(
"Block not found: {}"
.
format
(
exception
.
message
))
@view_auth_classes
()
class
BlocksInCourseView
(
BlocksView
):
"""
**Use Case**
Returns the blocks in the course according to the requesting user's access level.
**Example requests**:
GET /api/courses/v1/blocks/?course_id=<course_id>
GET /api/courses/v1/blocks/?course_id=<course_id>
&user=anjali,
&depth=all,
&requested_fields=graded,format,student_view_multi_device,
&block_counts=video,
&student_view_data=video,
**Parameters**:
* student_view_data: (list) Indicates for which block types to return student_view_data.
Example: student_view_data=video
* block_counts: (list) Indicates for which block types to return the aggregate count of the blocks.
Example: block_counts=video,problem
* requested_fields: (list) Indicates which additional fields to return for each block.
The following fields are always returned: type, display_name
Example: requested_fields=graded,format,student_view_multi_device
* depth (integer or all) Indicates how deep to traverse into the blocks hierarchy.
A value of all means the entire hierarchy.
Default is 0
Example: depth=all
* nav_depth (integer) Indicates how far deep to traverse into the course hierarchy before bundling
all the descendants.
Default is 3 since typical navigational views of the course show a maximum of chapter->sequential->vertical.
Example: nav_depth=3
* return_type (string) Indicates in what data type to return the blocks.
Default is dict. Supported values are: dict, list
Example: return_type=dict
**Response Values**
The following fields are returned with a successful response.
* root: The ID of the root node of the course blocks.
* blocks: A dictionary that maps block usage IDs to a collection of information about each block.
Each block contains the following fields.
* id: (string) The usage ID of the block.
* type: (string) The type of block. Possible values include course, chapter, sequential, vertical, html,
problem, video, and discussion. The type can also be the name of a custom type of block used for the course.
* display_name: (string) The display name of the block.
* children: (list) If the block has child blocks, a list of IDs of the child blocks.
Returned only if "children" is included in the "requested_fields" parameter.
* block_counts: (dict) For each block type specified in the block_counts parameter to the endpoint, the
aggregate number of blocks of that type for this block and all of its descendants.
Returned only if the "block_counts" input parameter contains this block's type.
* graded (boolean) Whether or not the block or any of its descendants is graded.
Returned only if "graded" is included in the "requested_fields" parameter.
* format: (string) The assignment type of the block.
Possible values can be "Homework", "Lab", "Midterm Exam", and "Final Exam".
Returned only if "format" is included in the "requested_fields" parameter.
* student_view_data: (dict) The JSON data for this block.
Returned only if the "student_view_data" input parameter contains this block's type.
* student_view_url: (string) The URL to retrieve the HTML rendering of this block's student view.
The HTML could include CSS and Javascript code. This field can be used in combination with the
student_view_multi_device field to decide whether to display this content to the user.
This URL can be used as a fallback if the student_view_data for this block type is not supported by
the client or the block.
* student_view_multi_device: (boolean) Whether or not the block's rendering obtained via block_url has support
for multiple devices.
Returned only if "student_view_multi_device" is included in the "requested_fields" parameter.
* lms_web_url: (string) The URL to the navigational container of the xBlock on the web LMS.
This URL can be used as a further fallback if the student_view_url and the student_view_data fields
are not supported.
"""
def
list
(
self
,
request
):
# convert the requested course_key to the course's root block's usage_key
course_key_string
=
request
.
QUERY_PARAMS
.
get
(
'course_id'
,
None
)
if
not
course_key_string
:
raise
ValidationError
(
'course_id is required.'
)
try
:
course_key
=
CourseKey
.
from_string
(
course_key_string
)
course_usage_key
=
modulestore
()
.
make_course_usage_key
(
course_key
)
return
super
(
BlocksInCourseView
,
self
)
.
list
(
request
,
course_usage_key
)
except
InvalidKeyError
:
raise
ValidationError
(
"'{}' is not a valid course key."
.
format
(
unicode
(
course_key_string
)))
lms/djangoapps/course_api/urls.py
View file @
691fed7a
...
...
@@ -9,6 +9,6 @@ from .views import CourseView
urlpatterns
=
patterns
(
''
,
url
(
r'^v1/course/{}'
.
format
(
settings
.
COURSE_KEY_PATTERN
),
CourseView
.
as_view
(),
name
=
"course_detail"
),
url
(
r'
^v1/blocks/
'
,
include
(
'course_api.blocks.urls'
))
url
(
r'^v1/course
s
/{}'
.
format
(
settings
.
COURSE_KEY_PATTERN
),
CourseView
.
as_view
(),
name
=
"course_detail"
),
url
(
r''
,
include
(
'course_api.blocks.urls'
))
)
lms/djangoapps/course_api/views.py
View file @
691fed7a
...
...
@@ -22,7 +22,7 @@ class CourseView(APIView):
return
Response
({
'blocks_url'
:
reverse
(
'
course_blocks
'
,
'
blocks_in_block_tree
'
,
kwargs
=
{
'usage_key_string'
:
unicode
(
course_usage_key
)},
request
=
request
,
)
...
...
lms/urls.py
View file @
691fed7a
...
...
@@ -76,7 +76,7 @@ urlpatterns = (
url
(
r'^api/course_structure/'
,
include
(
'course_structure_api.urls'
,
namespace
=
'course_structure_api'
)),
# Course API
url
(
r'^api/course/'
,
include
(
'course_api.urls'
)),
url
(
r'^api/course
s
/'
,
include
(
'course_api.urls'
)),
# User API endpoints
url
(
r'^api/user/'
,
include
(
'openedx.core.djangoapps.user_api.urls'
)),
...
...
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