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
0eb88c0e
Commit
0eb88c0e
authored
Sep 23, 2015
by
Nimisha Asthagiri
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fixup! course_api unit tests.
parent
b7c4ada9
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
479 additions
and
64 deletions
+479
-64
lms/djangoapps/course_api/blocks/forms.py
+42
-19
lms/djangoapps/course_api/blocks/permissions.py
+24
-0
lms/djangoapps/course_api/blocks/serializers.py
+33
-6
lms/djangoapps/course_api/blocks/tests/test_forms.py
+46
-8
lms/djangoapps/course_api/blocks/tests/test_serializers.py
+142
-0
lms/djangoapps/course_api/blocks/tests/test_utils.py
+12
-0
lms/djangoapps/course_api/blocks/tests/test_views.py
+133
-0
lms/djangoapps/course_api/blocks/transformers/__init__.py
+3
-2
lms/djangoapps/course_api/blocks/transformers/blocks_api.py
+3
-3
lms/djangoapps/course_api/blocks/transformers/student_view.py
+1
-1
lms/djangoapps/course_api/blocks/views.py
+39
-25
openedx/core/djangoapps/util/test_forms.py
+1
-0
No files found.
lms/djangoapps/course_api/blocks/forms.py
View file @
0eb88c0e
...
...
@@ -3,17 +3,17 @@
"""
from
django.contrib.auth.models
import
User
from
django.core.exceptions
import
ValidationError
from
django.forms
import
Form
,
CharField
,
Field
,
MultipleHiddenInput
from
django.forms
import
Form
,
CharField
,
ChoiceField
,
Field
,
MultipleHiddenInput
from
django.http
import
Http404
from
rest_framework.exceptions
import
PermissionDenied
from
courseware.access
import
_has_access_to_course
from
xmodule.modulestore.django
import
modulestore
from
opaque_keys
import
InvalidKeyError
from
opaque_keys.edx.keys
import
UsageKey
from
transformers.student_view
import
StudentViewTransformer
from
transformers.block_counts
import
BlockCountsTransformer
from
.permissions
import
can_access_other_users_blocks
,
can_access_users_blocks
class
ListField
(
Field
):
...
...
@@ -33,6 +33,10 @@ class BlockListGetForm(Form):
student_view_data
=
ListField
(
required
=
False
)
block_counts
=
ListField
(
required
=
False
)
depth
=
CharField
(
required
=
False
)
return_type
=
ChoiceField
(
required
=
False
,
choices
=
[(
choice
,
choice
)
for
choice
in
[
'dict'
,
'list'
]],
)
def
clean_requested_fields
(
self
):
# add default requested_fields
...
...
@@ -60,37 +64,56 @@ class BlockListGetForm(Form):
return
usage_key
def
clean
(
self
):
cleaned_data
=
super
(
BlockListGetForm
,
self
)
.
clean
()
# add additional requested_fields that are specified as separate parameters, if they were requested
for
additional_field
in
[
StudentViewTransformer
.
STUDENT_VIEW_DATA
,
BlockCountsTransformer
.
BLOCK_COUNTS
]:
if
cleaned_data
.
get
(
additional_field
):
cleaned_data
[
'requested_fields'
]
.
add
(
additional_field
)
usage_key
=
self
.
cleaned_data
.
get
(
'usage_key'
)
if
not
usage_key
:
return
def
clean_return_type
(
self
):
"""
Return valid 'return_type' or default value of 'dict'
"""
return
self
.
cleaned_data
[
'return_type'
]
or
'dict'
# validate and set user
def
clean_requested_user
(
self
,
cleaned_data
,
course_key
):
"""
Validates and returns the requested_user, while checking permissions.
"""
requested_username
=
cleaned_data
.
get
(
'user'
,
''
)
requesting_user
=
self
.
initial
[
'requesting_user'
]
if
requesting_user
.
username
.
lower
()
==
requested_username
.
lower
():
cleaned_data
[
'user'
]
=
requesting_user
requested_user
=
requesting_user
else
:
# the requesting user is trying to access another user's view
# verify requesting user
is staff and update requested user's object
if
not
_has_access_to_course
(
requesting_user
,
'staff'
,
usage_key
.
course_key
):
# verify requesting user
can access another user's blocks
if
not
can_access_other_users_blocks
(
requesting_user
,
course_key
):
raise
PermissionDenied
(
"'{requesting_username}' does not have permission to access view for '{requested_username}'."
.
format
(
requesting_username
=
requesting_user
.
username
,
requested_username
=
requested_username
)
)
#
get
requested user object
#
update
requested user object
try
:
cleaned_data
[
'user'
]
=
User
.
objects
.
get
(
username
=
requested_username
)
requested_user
=
User
.
objects
.
get
(
username
=
requested_username
)
except
(
User
.
DoesNotExist
):
raise
Http404
(
"'{username}' does not exist."
.
format
(
username
=
requested_username
))
# verify whether the requested user's blocks can be accessed
if
not
can_access_users_blocks
(
requested_user
,
course_key
):
raise
PermissionDenied
(
"Course blocks for '{requested_username}' cannot be accessed."
.
format
(
requested_username
=
requested_username
)
)
return
requested_user
def
clean
(
self
):
cleaned_data
=
super
(
BlockListGetForm
,
self
)
.
clean
()
# add additional requested_fields that are specified as separate parameters, if they were requested
for
additional_field
in
[
StudentViewTransformer
.
STUDENT_VIEW_DATA
,
BlockCountsTransformer
.
BLOCK_COUNTS
]:
if
cleaned_data
.
get
(
additional_field
):
cleaned_data
[
'requested_fields'
]
.
add
(
additional_field
)
usage_key
=
cleaned_data
.
get
(
'usage_key'
)
if
not
usage_key
:
return
cleaned_data
[
'user'
]
=
self
.
clean_requested_user
(
cleaned_data
,
usage_key
.
course_key
)
return
cleaned_data
lms/djangoapps/course_api/blocks/permissions.py
0 → 100644
View file @
0eb88c0e
"""
Encapsulates permissions checks for Course Blocks API
"""
from
courseware.access
import
has_access
from
student.models
import
CourseEnrollment
def
can_access_other_users_blocks
(
requesting_user
,
course_key
):
"""
Returns whether the requesting_user can access the blocks for
other users in the given course.
"""
return
has_access
(
requesting_user
,
'staff'
,
course_key
)
def
can_access_users_blocks
(
requested_user
,
course_key
):
"""
Returns whether blocks for the requested_user is accessible.
"""
return
(
(
requested_user
.
id
and
CourseEnrollment
.
is_enrolled
(
requested_user
,
course_key
))
or
has_access
(
requested_user
,
'staff'
,
course_key
)
)
lms/djangoapps/course_api/blocks/serializers.py
View file @
0eb88c0e
...
...
@@ -7,15 +7,20 @@ from rest_framework.reverse import reverse
from
transformers
import
SUPPORTED_FIELDS
# TODO support depth parameter
class
BlockSerializer
(
serializers
.
Serializer
):
"""
TODO
Serializer for single course block
"""
def
_get_field
(
self
,
block_key
,
transformer
,
field_name
):
def
_get_field
(
self
,
block_key
,
transformer
,
field_name
,
default
):
if
transformer
:
return
self
.
context
[
'block_structure'
]
.
get_transformer_block_data
(
block_key
,
transformer
,
field_name
)
value
=
self
.
context
[
'block_structure'
]
.
get_transformer_block_data
(
block_key
,
transformer
,
field_name
)
else
:
return
self
.
context
[
'block_structure'
]
.
get_xblock_field
(
block_key
,
field_name
)
value
=
self
.
context
[
'block_structure'
]
.
get_xblock_field
(
block_key
,
field_name
)
# TODO should we return falsey values in the response?
# for example, if student_view_multi_device is false, just don't specify it?
return
value
if
(
value
is
not
None
)
else
default
def
to_native
(
self
,
block_key
):
# create response data dict for basic fields
...
...
@@ -36,13 +41,35 @@ class BlockSerializer(serializers.Serializer):
# add additional requested fields that are supported by the various transformers
for
supported_field
in
SUPPORTED_FIELDS
:
if
supported_field
.
requested_field_name
in
self
.
context
[
'requested_fields'
]:
data
[
supported_field
.
requested_field_name
]
=
self
.
_get_field
(
field_value
=
self
.
_get_field
(
block_key
,
supported_field
.
transformer
,
supported_field
.
block_field_name
,
supported_field
.
default_value
,
)
if
field_value
is
not
None
:
data
[
supported_field
.
requested_field_name
]
=
field_value
if
'children'
in
self
.
context
[
'requested_fields'
]:
data
[
'children'
]
=
self
.
context
[
'block_structure'
]
.
get_children
(
block_key
)
children
=
self
.
context
[
'block_structure'
]
.
get_children
(
block_key
)
if
children
:
data
[
'children'
]
=
children
return
data
class
BlockDictSerializer
(
serializers
.
Serializer
):
"""
Serializer that formats to a dictionary, rather than a list, of blocks
"""
root
=
serializers
.
CharField
(
source
=
'root_block_key'
)
blocks
=
serializers
.
SerializerMethodField
(
'get_blocks'
)
def
get_blocks
(
self
,
structure
):
"""
Serialize to a dictionary of blocks keyed by the block's usage_key.
"""
return
{
unicode
(
block_key
):
BlockSerializer
(
block_key
,
context
=
self
.
context
)
.
data
for
block_key
in
structure
}
lms/djangoapps/course_api/blocks/tests/test_forms.py
View file @
0eb88c0e
...
...
@@ -3,43 +3,52 @@ Tests for Course Blocks forms
"""
import
ddt
from
django.http
import
Http404
,
QueryDict
from
unittest
import
TestCase
from
urllib
import
urlencode
from
rest_framework.exceptions
import
PermissionDenied
from
student.
tests.factories
import
UserFactory
from
opaque_keys.edx.keys
import
UsageKe
y
from
student.
models
import
CourseEnrollment
from
student.tests.factories
import
UserFactory
,
CourseEnrollmentFactor
y
from
openedx.core.djangoapps.util.test_forms
import
FormTestMixin
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
from
xmodule.modulestore.tests.factories
import
CourseFactory
from
..forms
import
BlockListGetForm
@ddt.ddt
class
TestBlockListGetForm
(
FormTestMixin
,
TestCase
):
class
TestBlockListGetForm
(
FormTestMixin
,
ModuleStore
TestCase
):
"""
Tests for BlockListGetForm
"""
FORM_CLASS
=
BlockListGetForm
def
setUp
(
self
):
super
(
TestBlockListGetForm
,
self
)
.
setUp
()
self
.
student
=
UserFactory
.
create
()
self
.
student2
=
UserFactory
.
create
()
self
.
staff
=
UserFactory
.
create
(
is_staff
=
True
)
self
.
initial
=
{
'requesting_user'
:
self
.
student
}
usage_key_string
=
'block-v1:some_org+split_course+some_run+type@some_block_type+block@some_block_id'
self
.
course
=
CourseFactory
.
create
()
usage_key
=
self
.
course
.
location
CourseEnrollmentFactory
.
create
(
user
=
self
.
student
,
course_id
=
self
.
course
.
id
)
CourseEnrollmentFactory
.
create
(
user
=
self
.
student2
,
course_id
=
self
.
course
.
id
)
self
.
initial
=
{
'requesting_user'
:
self
.
student
}
self
.
form_data
=
QueryDict
(
urlencode
({
'user'
:
self
.
student
.
username
,
'usage_key'
:
u
sage_key_string
,
'usage_key'
:
u
nicode
(
usage_key
)
,
}),
mutable
=
True
,
)
self
.
cleaned_data
=
{
'block_counts'
:
[],
'depth'
:
0
,
'return_type'
:
'dict'
,
'requested_fields'
:
{
'display_name'
,
'type'
},
'student_view_data'
:
[],
'usage_key'
:
UsageKey
.
from_string
(
usage_key_string
)
,
'usage_key'
:
usage_key
,
'user'
:
self
.
student
,
}
...
...
@@ -91,6 +100,21 @@ class TestBlockListGetForm(FormTestMixin, TestCase):
self
.
initial
=
{
'requesting_user'
:
self
.
staff
}
self
.
get_form
(
expected_valid
=
True
)
def
test_unenrolled_student
(
self
):
CourseEnrollment
.
unenroll
(
self
.
student
,
self
.
course
.
id
)
self
.
assert_raises_permission_denied
()
def
test_unenrolled_staff
(
self
):
CourseEnrollment
.
unenroll
(
self
.
staff
,
self
.
course
.
id
)
self
.
initial
=
{
'requesting_user'
:
self
.
staff
}
self
.
form_data
[
'user'
]
=
self
.
staff
.
username
self
.
get_form
(
expected_valid
=
True
)
def
test_unenrolled_student_by_staff
(
self
):
CourseEnrollment
.
unenroll
(
self
.
student
,
self
.
course
.
id
)
self
.
initial
=
{
'requesting_user'
:
self
.
staff
}
self
.
assert_raises_permission_denied
()
#-- depth
def
test_depth_integer
(
self
):
...
...
@@ -107,6 +131,20 @@ class TestBlockListGetForm(FormTestMixin, TestCase):
self
.
form_data
[
'depth'
]
=
'not_an_integer'
self
.
assert_error
(
'depth'
,
"'not_an_integer' is not a valid depth value."
)
#-- return_type
def
test_return_type
(
self
):
self
.
form_data
[
'return_type'
]
=
'list'
self
.
cleaned_data
[
'return_type'
]
=
'list'
self
.
assert_equals_cleaned_data
()
def
test_return_type_invalid
(
self
):
self
.
form_data
[
'return_type'
]
=
'invalid_return_type'
self
.
assert_error
(
'return_type'
,
"Select a valid choice. invalid_return_type is not one of the available choices."
)
#-- requested fields
def
test_requested_fields
(
self
):
...
...
lms/djangoapps/course_api/blocks/tests/test_serializers.py
0 → 100644
View file @
0eb88c0e
"""
Tests for Course Blocks serializers
"""
from
mock
import
MagicMock
from
student.tests.factories
import
UserFactory
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
from
lms.djangoapps.course_blocks.api
import
get_course_blocks
,
LMS_COURSE_TRANSFORMERS
from
..transformers.blocks_api
import
BlocksAPITransformer
from
..serializers
import
BlockSerializer
,
BlockDictSerializer
from
.test_utils
import
deserialize_usage_key
class
TestBlockSerializerBase
(
ModuleStoreTestCase
):
"""
Base class for testing BlockSerializer and BlockDictSerializer
"""
def
setUp
(
self
):
super
(
TestBlockSerializerBase
,
self
)
.
setUp
()
self
.
course_key
=
self
.
create_toy_course
()
self
.
user
=
UserFactory
.
create
()
blocks_api_transformer
=
BlocksAPITransformer
(
block_types_to_count
=
[
'video'
],
requested_student_view_data
=
[
'video'
],
)
self
.
block_structure
=
get_course_blocks
(
self
.
user
,
course_key
=
self
.
course_key
,
transformers
=
LMS_COURSE_TRANSFORMERS
+
[
blocks_api_transformer
],
)
self
.
serializer_context
=
{
'request'
:
MagicMock
(),
'block_structure'
:
self
.
block_structure
,
'requested_fields'
:
[
'type'
],
}
def
assert_basic_block
(
self
,
block_key_string
,
serialized_block
):
"""
Verifies the given serialized_block when basic fields are requested.
"""
block_key
=
deserialize_usage_key
(
block_key_string
,
self
.
course_key
)
self
.
assertEquals
(
self
.
block_structure
.
get_xblock_field
(
block_key
,
'category'
),
serialized_block
[
'type'
],
)
self
.
assertEquals
(
set
(
serialized_block
.
iterkeys
()),
{
'id'
,
'type'
,
'lms_web_url'
,
'student_view_url'
},
)
def
add_additional_requested_fields
(
self
):
"""
Adds additional fields to the requested_fields context for the serializer.
"""
self
.
serializer_context
[
'requested_fields'
]
.
extend
([
'children'
,
'display_name'
,
'graded'
,
'format'
,
'block_counts'
,
'student_view_data'
,
'student_view_multi_device'
,
])
def
assert_extended_block
(
self
,
serialized_block
):
"""
Verifies the given serialized_block when additional fields are requested.
"""
self
.
assertLessEqual
(
{
'id'
,
'type'
,
'lms_web_url'
,
'student_view_url'
,
'display_name'
,
'graded'
,
'block_counts'
,
'student_view_multi_device'
,
},
set
(
serialized_block
.
iterkeys
()),
)
# video blocks should have student_view_data
if
serialized_block
[
'type'
]
==
'video'
:
self
.
assertTrue
(
'student_view_data'
in
serialized_block
)
# html blocks should have student_view_multi_device set to True
if
serialized_block
[
'type'
]
==
'html'
:
self
.
assertTrue
(
serialized_block
[
'student_view_multi_device'
])
class
TestBlockSerializer
(
TestBlockSerializerBase
):
"""
Tests the BlockSerializer class, which returns a list of blocks.
"""
def
create_serializer
(
self
):
"""
creates a BlockSerializer
"""
return
BlockSerializer
(
self
.
block_structure
,
many
=
True
,
context
=
self
.
serializer_context
,
)
def
test_basic
(
self
):
serializer
=
self
.
create_serializer
()
for
serialized_block
in
serializer
.
data
:
self
.
assert_basic_block
(
serialized_block
[
'id'
],
serialized_block
)
def
test_additional_requested_fields
(
self
):
self
.
add_additional_requested_fields
()
serializer
=
self
.
create_serializer
()
for
serialized_block
in
serializer
.
data
:
self
.
assert_extended_block
(
serialized_block
)
class
TestBlockDictSerializer
(
TestBlockSerializerBase
):
"""
Tests the BlockDictSerializer class, which returns a dict of blocks key-ed by its block_key.
"""
def
create_serializer
(
self
):
"""
creates a BlockDictSerializer
"""
return
BlockDictSerializer
(
self
.
block_structure
,
many
=
False
,
context
=
self
.
serializer_context
,
)
def
test_basic
(
self
):
serializer
=
self
.
create_serializer
()
# verify root
self
.
assertEquals
(
serializer
.
data
[
'root'
],
unicode
(
self
.
block_structure
.
root_block_key
))
# verify blocks
for
block_key_string
,
serialized_block
in
serializer
.
data
[
'blocks'
]
.
iteritems
():
self
.
assertEquals
(
serialized_block
[
'id'
],
block_key_string
)
self
.
assert_basic_block
(
block_key_string
,
serialized_block
)
def
test_additional_requested_fields
(
self
):
self
.
add_additional_requested_fields
()
serializer
=
self
.
create_serializer
()
for
serialized_block
in
serializer
.
data
[
'blocks'
]
.
itervalues
():
self
.
assert_extended_block
(
serialized_block
)
lms/djangoapps/course_api/blocks/tests/test_utils.py
0 → 100644
View file @
0eb88c0e
"""
Helper functions for unit tests
"""
from
opaque_keys.edx.keys
import
UsageKey
def
deserialize_usage_key
(
usage_key_string
,
course_key
):
"""
Returns the deserialized UsageKey object of the given usage_key_string for the given course.
"""
return
UsageKey
.
from_string
(
usage_key_string
)
.
replace
(
course_key
=
course_key
)
lms/djangoapps/course_api/blocks/tests/test_views.py
0 → 100644
View file @
0eb88c0e
"""
Tests for Course Blocks views
"""
from
django.core.urlresolvers
import
reverse
from
student.models
import
CourseEnrollment
from
student.tests.factories
import
CourseEnrollmentFactory
,
UserFactory
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
from
.test_utils
import
deserialize_usage_key
class
TestCourseBlocksView
(
ModuleStoreTestCase
):
"""
Test class for CourseBlocks view
"""
def
setUp
(
self
):
super
(
TestCourseBlocksView
,
self
)
.
setUp
()
self
.
user
=
UserFactory
.
create
()
self
.
client
.
login
(
username
=
self
.
user
.
username
,
password
=
'test'
)
self
.
course_key
=
self
.
create_toy_course
()
self
.
course_usage_key
=
self
.
store
.
make_course_usage_key
(
self
.
course_key
)
CourseEnrollmentFactory
.
create
(
user
=
self
.
user
,
course_id
=
self
.
course_key
)
self
.
non_orphaned_block_usage_keys
=
set
(
unicode
(
item
.
location
)
for
item
in
self
.
store
.
get_items
(
self
.
course_key
)
# remove all orphaned items in the course, except for the root 'course' block
if
self
.
store
.
get_parent_location
(
item
.
location
)
or
item
.
category
==
'course'
)
self
.
url
=
reverse
(
'course_blocks'
,
kwargs
=
{
'usage_key_string'
:
unicode
(
self
.
course_usage_key
)}
)
def
verify_response
(
self
,
expected_status_code
=
200
,
params
=
None
):
query_params
=
{
'user'
:
self
.
user
.
username
}
if
params
:
query_params
.
update
(
params
)
response
=
self
.
client
.
get
(
self
.
url
,
query_params
)
self
.
assertEquals
(
response
.
status_code
,
expected_status_code
)
return
response
def
verify_response_block_list
(
self
,
response
):
self
.
assertSetEqual
(
set
(
block
[
'id'
]
for
block
in
response
.
data
),
self
.
non_orphaned_block_usage_keys
,
)
def
verify_response_block_dict
(
self
,
response
):
self
.
assertSetEqual
(
set
(
response
.
data
[
'blocks'
]
.
iterkeys
()),
self
.
non_orphaned_block_usage_keys
,
)
def
assert_in_iff
(
self
,
member
,
container
,
predicate
):
if
predicate
:
self
.
assertIn
(
member
,
container
)
else
:
self
.
assertNotIn
(
member
,
container
)
def
assert_true_iff
(
self
,
expression
,
predicate
):
if
predicate
:
self
.
assertTrue
(
expression
)
else
:
self
.
assertFalse
(
expression
)
def
test_not_authenticated
(
self
):
self
.
client
.
logout
()
self
.
verify_response
(
401
)
def
test_not_enrolled
(
self
):
CourseEnrollment
.
unenroll
(
self
.
user
,
self
.
course_key
)
self
.
verify_response
(
403
)
def
test_non_existent_course
(
self
):
self
.
store
.
delete_course
(
self
.
course_key
,
self
.
user
)
self
.
verify_response
(
404
)
def
test_basic
(
self
):
response
=
self
.
verify_response
()
self
.
assertEquals
(
response
.
data
[
'root'
],
unicode
(
self
.
course_usage_key
))
self
.
verify_response_block_dict
(
response
)
for
block_key_string
,
block_data
in
response
.
data
[
'blocks'
]
.
iteritems
():
block_key
=
deserialize_usage_key
(
block_key_string
,
self
.
course_key
)
self
.
assertEquals
(
block_data
[
'id'
],
block_key_string
)
self
.
assertEquals
(
block_data
[
'type'
],
block_key
.
block_type
)
self
.
assertEquals
(
block_data
[
'display_name'
],
self
.
store
.
get_item
(
block_key
)
.
display_name
or
''
)
def
test_return_type_param
(
self
):
response
=
self
.
verify_response
(
params
=
{
'return_type'
:
'list'
})
self
.
verify_response_block_list
(
response
)
def
test_block_counts_param
(
self
):
response
=
self
.
verify_response
(
params
=
{
'block_counts'
:
[
'course'
,
'chapter'
]})
self
.
verify_response_block_dict
(
response
)
for
block_data
in
response
.
data
[
'blocks'
]
.
itervalues
():
self
.
assertEquals
(
block_data
[
'block_counts'
][
'course'
],
1
if
block_data
[
'type'
]
==
'course'
else
0
,
)
self
.
assertEquals
(
block_data
[
'block_counts'
][
'chapter'
],
(
1
if
block_data
[
'type'
]
==
'chapter'
else
5
if
block_data
[
'type'
]
==
'course'
else
0
)
)
def
test_student_view_data_param
(
self
):
response
=
self
.
verify_response
(
params
=
{
'student_view_data'
:
[
'video'
,
'chapter'
]})
self
.
verify_response_block_dict
(
response
)
for
block_data
in
response
.
data
[
'blocks'
]
.
itervalues
():
self
.
assert_in_iff
(
'student_view_data'
,
block_data
,
block_data
[
'type'
]
==
'video'
)
def
test_requested_fields_param
(
self
):
response
=
self
.
verify_response
(
params
=
{
'requested_fields'
:
[
'graded'
,
'format'
,
'student_view_multi_device'
,
'children'
,
'not_a_field'
]}
)
self
.
verify_response_block_dict
(
response
)
for
block_key_string
,
block_data
in
response
.
data
[
'blocks'
]
.
iteritems
():
block_key
=
deserialize_usage_key
(
block_key_string
,
self
.
course_key
)
xblock
=
self
.
store
.
get_item
(
block_key
)
self
.
assert_in_iff
(
'children'
,
block_data
,
xblock
.
has_children
)
self
.
assert_in_iff
(
'graded'
,
block_data
,
xblock
.
graded
is
not
None
)
self
.
assert_in_iff
(
'format'
,
block_data
,
xblock
.
format
is
not
None
)
self
.
assert_true_iff
(
block_data
[
'student_view_multi_device'
],
block_data
[
'type'
]
==
'html'
)
self
.
assertNotIn
(
'not_a_field'
,
block_data
)
lms/djangoapps/course_api/blocks/transformers/__init__.py
View file @
0eb88c0e
...
...
@@ -7,15 +7,16 @@ from block_counts import BlockCountsTransformer
class
SupportedFieldType
(
object
):
def
__init__
(
self
,
block_field_name
,
transformer
=
None
,
requested_field_name
=
None
):
def
__init__
(
self
,
block_field_name
,
transformer
=
None
,
requested_field_name
=
None
,
default_value
=
None
):
self
.
transformer
=
transformer
self
.
block_field_name
=
block_field_name
self
.
requested_field_name
=
requested_field_name
or
block_field_name
self
.
default_value
=
default_value
SUPPORTED_FIELDS
=
(
SupportedFieldType
(
'category'
,
None
,
'type'
),
SupportedFieldType
(
'display_name'
),
SupportedFieldType
(
'display_name'
,
default_value
=
''
),
SupportedFieldType
(
'graded'
),
SupportedFieldType
(
'format'
),
...
...
lms/djangoapps/course_api/blocks/transformers/blocks_api.py
View file @
0eb88c0e
...
...
@@ -11,8 +11,8 @@ class BlocksAPITransformer(BlockStructureTransformer):
STUDENT_VIEW_DATA
=
'student_view_data'
STUDENT_VIEW_MULTI_DEVICE
=
'student_view_multi_device'
def
__init__
(
self
,
block_
counts
,
requested_student_view_data
):
self
.
block_
counts
=
block_counts
def
__init__
(
self
,
block_
types_to_count
,
requested_student_view_data
):
self
.
block_
types_to_count
=
block_types_to_count
self
.
requested_student_view_data
=
requested_student_view_data
@classmethod
...
...
@@ -35,4 +35,4 @@ class BlocksAPITransformer(BlockStructureTransformer):
Mutates block_structure based on the given user_info.
"""
StudentViewTransformer
(
self
.
requested_student_view_data
)
.
transform
(
user_info
,
block_structure
)
BlockCountsTransformer
(
self
.
block_
counts
)
.
transform
(
user_info
,
block_structure
)
BlockCountsTransformer
(
self
.
block_
types_to_count
)
.
transform
(
user_info
,
block_structure
)
lms/djangoapps/course_api/blocks/transformers/student_view.py
View file @
0eb88c0e
...
...
@@ -62,5 +62,5 @@ class StudentViewTransformer(BlockStructureTransformer):
Mutates block_structure based on the given user_info.
"""
for
block_key
in
block_structure
.
post_order_traversal
():
if
block_structure
.
get_xblock_field
(
block_key
,
'
type
'
)
not
in
self
.
requested_student_view_data
:
if
block_structure
.
get_xblock_field
(
block_key
,
'
category
'
)
not
in
self
.
requested_student_view_data
:
block_structure
.
remove_transformer_block_data
(
block_key
,
self
,
self
.
STUDENT_VIEW_DATA
)
lms/djangoapps/course_api/blocks/views.py
View file @
0eb88c0e
from
django.core.exceptions
import
ValidationError
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
openedx.core.lib.api.view_utils
import
view_auth_classes
,
DeveloperErrorViewMixin
from
xmodule.modulestore.exceptions
import
ItemNotFoundError
from
transformers.blocks_api
import
BlocksAPITransformer
from
transformers.block_counts
import
BlockCountsTransformer
from
transformers.student_view
import
StudentViewTransformer
from
.forms
import
BlockListGetForm
from
.serializers
import
BlockSerializer
from
.serializers
import
BlockSerializer
,
BlockDictSerializer
@view_auth_classes
()
...
...
@@ -39,10 +41,10 @@ class CourseBlocks(DeveloperErrorViewMixin, ListAPIView):
Example: block_counts=video,problem
* fields: (list) Indicates which additional fields to return for each block.
*
requested_
fields: (list) Indicates which additional fields to return for each block.
The following fields are always returned: type, display_name
Example: fields=graded,format,student_view_multi_device,children
Example:
requested_
fields=graded,format,student_view_multi_device,children
* depth (integer or all) Indicates how deep to traverse into the blocks hierarchy.
A value of all means the entire hierarchy.
...
...
@@ -50,6 +52,11 @@ class CourseBlocks(DeveloperErrorViewMixin, ListAPIView):
Example: depth=all
* 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.
...
...
@@ -67,18 +74,18 @@ class CourseBlocks(DeveloperErrorViewMixin, ListAPIView):
* 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 "fields" parameter.
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 "fields" parameter.
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 "fields" parameter.
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.
...
...
@@ -92,8 +99,7 @@ class CourseBlocks(DeveloperErrorViewMixin, ListAPIView):
* 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 "fields" parameter.
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
...
...
@@ -109,30 +115,38 @@ class CourseBlocks(DeveloperErrorViewMixin, ListAPIView):
request - Django request object
course - course module object
"""
# request parameters
requested_params
=
request
.
GET
.
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
(
BlockCountsTransformer
.
BLOCK_COUNTS
,
[]),
params
.
cleaned_data
.
get
(
StudentViewTransformer
.
STUDENT_VIEW_DATA
,
[]),
)
blocks
=
get_course_blocks
(
params
.
cleaned_data
[
'user'
],
params
.
cleaned_data
[
'usage_key'
],
transformers
=
LMS_COURSE_TRANSFORMERS
+
[
blocks_api_transformer
],
)
return
Response
(
BlockSerializer
(
blocks
,
context
=
{
'request'
:
request
,
'block_structure'
:
blocks
,
'requested_fields'
:
params
.
cleaned_data
[
'requested_fields'
],
},
many
=
True
,
)
.
data
)
try
:
blocks
=
get_course_blocks
(
params
.
cleaned_data
[
'user'
],
params
.
cleaned_data
[
'usage_key'
],
transformers
=
LMS_COURSE_TRANSFORMERS
+
[
blocks_api_transformer
],
)
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
)
openedx/core/djangoapps/util/test_forms.py
View file @
0eb88c0e
...
...
@@ -2,6 +2,7 @@
Mixins for testing forms.
"""
class
FormTestMixin
(
object
):
"""A mixin for testing forms"""
def
get_form
(
self
,
expected_valid
):
...
...
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