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
282220da
Commit
282220da
authored
Sep 12, 2017
by
Michael Roytman
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
refactor and add multiple filter functionality
parent
d62e2498
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
427 additions
and
186 deletions
+427
-186
cms/djangoapps/contentstore/views/assets.py
+368
-177
cms/djangoapps/contentstore/views/tests/test_assets.py
+39
-8
cms/envs/common.py
+20
-1
No files found.
cms/djangoapps/contentstore/views/assets.py
View file @
282220da
...
@@ -28,17 +28,22 @@ from util.json_request import JsonResponse
...
@@ -28,17 +28,22 @@ from util.json_request import JsonResponse
__all__
=
[
'assets_handler'
]
__all__
=
[
'assets_handler'
]
REQUEST_DEFAULTS
=
{
# pylint: disable=unused-argument
'page'
:
0
,
'page_size'
:
50
,
'sort'
:
'date_added'
,
'direction'
:
''
,
'asset_type'
:
''
}
@login_required
@login_required
@ensure_csrf_cookie
@ensure_csrf_cookie
def
assets_handler
(
request
,
course_key_string
=
None
,
asset_key_string
=
None
):
def
assets_handler
(
request
,
course_key_string
=
None
,
asset_key_string
=
None
):
"""
'''
The restful handler for assets.
The restful handler for assets.
It allows retrieval of all the assets (as an HTML page), as well as uploading new assets,
It allows retrieval of all the assets (as an HTML page), as well as uploading new assets,
deleting assets, and changing the
"locked"
state of an asset.
deleting assets, and changing the
'locked'
state of an asset.
GET
GET
html: return an html page which will show all course assets. Note that only the asset container
html: return an html page which will show all course assets. Note that only the asset container
...
@@ -46,38 +51,47 @@ def assets_handler(request, course_key_string=None, asset_key_string=None):
...
@@ -46,38 +51,47 @@ def assets_handler(request, course_key_string=None, asset_key_string=None):
json: returns a page of assets. The following parameters are supported:
json: returns a page of assets. The following parameters are supported:
page: the desired page of results (defaults to 0)
page: the desired page of results (defaults to 0)
page_size: the number of items per page (defaults to 50)
page_size: the number of items per page (defaults to 50)
sort: the asset field to sort by (defaults to
"date_added"
)
sort: the asset field to sort by (defaults to
'date_added'
)
direction: the sort direction (defaults to
"descending"
)
direction: the sort direction (defaults to
'descending'
)
POST
POST
json: create (or update?) an asset. The only updating that can be done is changing the lock state.
json: create (or update?) an asset. The only updating that can be done is changing the lock state.
PUT
PUT
json: update the locked state of an asset
json: update the locked state of an asset
DELETE
DELETE
json: delete an asset
json: delete an asset
"""
'''
course_key
=
CourseKey
.
from_string
(
course_key_string
)
course_key
=
CourseKey
.
from_string
(
course_key_string
)
if
not
has_course_author_access
(
request
.
user
,
course_key
):
if
not
has_course_author_access
(
request
.
user
,
course_key
):
raise
PermissionDenied
()
raise
PermissionDenied
()
response_format
=
request
.
GET
.
get
(
'format'
)
or
request
.
POST
.
get
(
'format'
)
or
'html'
response_format
=
_get_response_format
(
request
)
if
response_format
==
'json'
or
'application/json'
in
request
.
META
.
get
(
'HTTP_ACCEPT'
,
'application/json'
):
if
_request_response_format_is_json
(
request
,
response_format
):
if
request
.
method
==
'GET'
:
if
request
.
method
==
'GET'
:
return
_assets_json
(
request
,
course_key
)
return
_assets_json
(
request
,
course_key
)
else
:
asset_key
=
AssetKey
.
from_string
(
asset_key_string
)
if
asset_key_string
else
None
asset_key
=
AssetKey
.
from_string
(
asset_key_string
)
if
asset_key_string
else
None
return
_update_asset
(
request
,
course_key
,
asset_key
)
return
_update_asset
(
request
,
course_key
,
asset_key
)
elif
request
.
method
==
'GET'
:
# assume html
elif
request
.
method
==
'GET'
:
# assume html
return
_asset_index
(
request
,
course_key
)
return
_asset_index
(
course_key
)
else
:
return
HttpResponseNotFound
()
return
HttpResponseNotFound
()
def
_get_response_format
(
request
):
return
request
.
GET
.
get
(
'format'
)
or
request
.
POST
.
get
(
'format'
)
or
'html'
def
_request_response_format_is_json
(
request
,
response_format
):
return
response_format
==
'json'
or
'application/json'
in
request
.
META
.
get
(
'HTTP_ACCEPT'
,
'application/json'
)
def
_asset_index
(
request
,
course_key
):
def
_asset_index
(
course_key
):
"""
'''
Display an editable asset library.
Display an editable asset library.
Supports start (0-based index into the list of assets) and max query parameters.
Supports start (0-based index into the list of assets) and max query parameters.
"""
'''
course_module
=
modulestore
()
.
get_course
(
course_key
)
course_module
=
modulestore
()
.
get_course
(
course_key
)
return
render_to_response
(
'asset_index.html'
,
{
return
render_to_response
(
'asset_index.html'
,
{
...
@@ -90,102 +104,202 @@ def _asset_index(request, course_key):
...
@@ -90,102 +104,202 @@ def _asset_index(request, course_key):
def
_assets_json
(
request
,
course_key
):
def
_assets_json
(
request
,
course_key
):
"""
'''
Display an editable asset library.
Display an editable asset library.
Supports start (0-based index into the list of assets) and max query parameters.
Supports start (0-based index into the list of assets) and max query parameters.
"""
'''
requested_page
=
int
(
request
.
GET
.
get
(
'page'
,
0
))
request_options
=
_parse_request_to_dictionary
(
request
)
requested_page_size
=
int
(
request
.
GET
.
get
(
'page_size'
,
50
))
requested_sort
=
request
.
GET
.
get
(
'sort'
,
'date_added'
)
requested_filter
=
request
.
GET
.
get
(
'asset_type'
,
''
)
requested_file_types
=
settings
.
FILES_AND_UPLOAD_TYPE_FILTERS
.
get
(
requested_filter
,
None
)
filter_params
=
None
if
requested_filter
:
if
requested_filter
==
'OTHER'
:
all_filters
=
settings
.
FILES_AND_UPLOAD_TYPE_FILTERS
where
=
[]
for
all_filter
in
all_filters
:
extension_filters
=
all_filters
[
all_filter
]
where
.
extend
(
[
"JSON.stringify(this.contentType).toUpperCase() != JSON.stringify('{}').toUpperCase()"
.
format
(
extension_filter
)
for
extension_filter
in
extension_filters
])
filter_params
=
{
"$where"
:
' && '
.
join
(
where
),
}
else
:
where
=
[
"JSON.stringify(this.contentType).toUpperCase() == JSON.stringify('{}').toUpperCase()"
.
format
(
req_filter
)
for
req_filter
in
requested_file_types
]
filter_params
=
{
"$where"
:
' || '
.
join
(
where
),
}
sort_direction
=
DESCENDING
filter_parameters
=
None
if
request
.
GET
.
get
(
'direction'
,
''
)
.
lower
()
==
'asc'
:
sort_direction
=
ASCENDING
# Convert the field name to the Mongo name
if
request_options
[
'requested_asset_type'
]:
if
requested_sort
==
'date_added'
:
filters_are_invalid_error
=
_get_error_if_invalid_parameters
(
request_options
[
'requested_asset_type'
])
requested_sort
=
'uploadDate'
elif
requested_sort
==
'display_name'
:
if
filters_are_invalid_error
is
not
None
:
requested_sort
=
'displayname'
return
filters_are_invalid_error
sort
=
[(
requested_sort
,
sort_direction
)]
filter_parameters
=
_get_filter_parameters_for_mongo
(
request_options
[
'requested_asset_type'
])
current_page
=
max
(
requested_page
,
0
)
sort_type_and_direction
=
_get_sort_type_and_direction
(
request_options
)
start
=
current_page
*
requested_page_size
options
=
{
requested_page_size
=
request_options
[
'requested_page_size'
]
current_page
=
_get_current_page
(
request_options
[
'requested_page'
])
first_asset_to_display_index
=
_get_first_asset_index
(
current_page
,
requested_page_size
)
query_options
=
{
'current_page'
:
current_page
,
'current_page'
:
current_page
,
'page_size'
:
requested_page_size
,
'page_size'
:
requested_page_size
,
'sort'
:
sort
,
'sort'
:
sort
_type_and_direction
,
'filter_params'
:
filter_params
'filter_params'
:
filter_param
eter
s
}
}
assets
,
total_count
=
_get_assets_for_page
(
request
,
course_key
,
options
)
end
=
start
+
len
(
assets
)
# If the query is beyond the final page, then re-query the final page so
# that at least one asset is returned
if
requested_page
>
0
and
start
>=
total_count
:
options
[
'current_page'
]
=
current_page
=
int
(
math
.
floor
((
total_count
-
1
)
/
requested_page_size
))
start
=
current_page
*
requested_page_size
assets
,
total_count
=
_get_assets_for_page
(
request
,
course_key
,
options
)
end
=
start
+
len
(
assets
)
asset_json
=
[]
for
asset
in
assets
:
asset_location
=
asset
[
'asset_key'
]
# note, due to the schema change we may not have a 'thumbnail_location'
# in the result set
thumbnail_location
=
asset
.
get
(
'thumbnail_location'
,
None
)
if
thumbnail_location
:
thumbnail_location
=
course_key
.
make_asset_key
(
'thumbnail'
,
thumbnail_location
[
4
])
asset_locked
=
asset
.
get
(
'locked'
,
False
)
asset_json
.
append
(
_get_asset_json
(
asset
[
'displayname'
],
asset
[
'contentType'
],
asset
[
'uploadDate'
],
asset_location
,
thumbnail_location
,
asset_locked
))
return
JsonResponse
({
assets
,
total_count
=
_get_assets_for_page
(
course_key
,
query_options
)
'start'
:
start
,
'end'
:
end
,
if
request_options
[
'requested_page'
]
>
0
and
first_asset_to_display_index
>=
total_count
:
_update_options_to_requery_final_page
(
query_options
,
total_count
)
current_page
=
query_options
[
'current_page'
]
first_asset_to_display_index
=
_get_first_asset_index
(
current_page
,
requested_page_size
)
assets
,
total_count
=
_get_assets_for_page
(
course_key
,
query_options
)
last_asset_to_display_index
=
first_asset_to_display_index
+
len
(
assets
)
assets_in_json_format
=
_get_assets_in_json_format
(
assets
,
course_key
)
response_payload
=
{
'start'
:
first_asset_to_display_index
,
'end'
:
last_asset_to_display_index
,
'page'
:
current_page
,
'page'
:
current_page
,
'pageSize'
:
requested_page_size
,
'pageSize'
:
requested_page_size
,
'totalCount'
:
total_count
,
'totalCount'
:
total_count
,
'assets'
:
asset_json
,
'assets'
:
assets_in_json_format
,
'sort'
:
requested_sort
,
'sort'
:
request_options
[
'requested_sort'
],
})
}
return
JsonResponse
(
response_payload
)
def
_parse_request_to_dictionary
(
request
):
return
{
'requested_page'
:
int
(
_get_requested_attribute
(
request
,
'page'
)),
'requested_page_size'
:
int
(
_get_requested_attribute
(
request
,
'page_size'
)),
'requested_sort'
:
_get_requested_attribute
(
request
,
'sort'
),
'requested_sort_direction'
:
_get_requested_attribute
(
request
,
'direction'
),
'requested_asset_type'
:
_get_requested_attribute
(
request
,
'asset_type'
)
}
def
_get_requested_attribute
(
request
,
attribute
):
return
request
.
GET
.
get
(
attribute
,
REQUEST_DEFAULTS
.
get
(
attribute
))
def
_get_error_if_invalid_parameters
(
requested_filter
):
requested_file_types
=
_get_requested_file_types_from_requested_filter
(
requested_filter
)
invalid_filters
=
[]
# OTHER is not described in the settings file as a filter
all_valid_file_types
=
set
(
_get_files_and_upload_type_filters
()
.
keys
())
all_valid_file_types
.
add
(
'OTHER'
)
for
requested_file_type
in
requested_file_types
:
if
requested_file_type
not
in
all_valid_file_types
:
invalid_filters
.
append
(
requested_file_type
)
if
invalid_filters
:
error_message
=
{
'error_code'
:
'invalid_asset_type_filter'
,
'developer_message'
:
'The asset_type parameter to the request is invalid. '
'The {} filters are not described in the settings.FILES_AND_UPLOAD_TYPE_FILTERS '
'dictionary.'
.
format
(
invalid_filters
)
}
return
JsonResponse
({
'error'
:
error_message
},
status
=
400
)
def
_get_filter_parameters_for_mongo
(
requested_filter
):
requested_file_types
=
_get_requested_file_types_from_requested_filter
(
requested_filter
)
mongo_where_operator_parameters
=
_get_mongo_where_operator_parameters_for_filters
(
requested_file_types
)
return
mongo_where_operator_parameters
def
_get_mongo_where_operator_parameters_for_filters
(
requested_file_types
):
javascript_filters
=
[]
for
requested_file_type
in
requested_file_types
:
if
requested_file_type
==
'OTHER'
:
javascript_filters_for_file_types
=
_get_javascript_expressions_for_other_
()
javascript_filters
.
append
(
javascript_filters_for_file_types
)
else
:
javascript_filters_for_file_types
=
_get_javascript_expressions_for_filter
(
requested_file_type
)
javascript_filters
.
append
(
javascript_filters_for_file_types
)
javascript_filters
=
_join_javascript_expressions_for_filters_with_separator
(
javascript_filters
,
'||'
)
return
_format_javascript_filters_for_mongo_where
(
javascript_filters
)
def
_format_javascript_filters_for_mongo_where
(
javascript_filters
):
return
{
'$where'
:
javascript_filters
,
}
def
_get_javascript_expressions_for_other_
():
file_extensions_for_requested_file_types
=
_get_files_and_upload_type_filters
()
.
values
()
file_extensions_for_requested_file_types_flattened
=
[
extension
for
extensions
in
file_extensions_for_requested_file_types
for
extension
in
extensions
]
javascript_expression_to_filter_extensions
=
_get_javascript_expressions_to_filter_extensions_with_operator
(
file_extensions_for_requested_file_types_flattened
,
'!='
)
joined_javascript_expressions_to_filter_extensions
=
_join_javascript_expressions_for_filters_with_separator
(
javascript_expression_to_filter_extensions
,
' && '
)
return
joined_javascript_expressions_to_filter_extensions
def
_get_javascript_expressions_for_filter
(
requested_file_type
):
file_extensions_for_requested_file_type
=
_get_extensions_for_file_type
(
requested_file_type
)
javascript_expressions_to_filter_extensions
=
_get_javascript_expressions_to_filter_extensions_with_operator
(
file_extensions_for_requested_file_type
,
'=='
)
joined_javascript_expressions_to_filter_extensions
=
_join_javascript_expressions_for_filters_with_separator
(
javascript_expressions_to_filter_extensions
,
' || '
)
return
joined_javascript_expressions_to_filter_extensions
def
_get_files_and_upload_type_filters
():
return
settings
.
FILES_AND_UPLOAD_TYPE_FILTERS
def
_get_requested_file_types_from_requested_filter
(
requested_filter
):
return
requested_filter
.
split
(
','
)
def
_get_extensions_for_file_type
(
requested_file_type
):
return
_get_files_and_upload_type_filters
()
.
get
(
requested_file_type
)
def
_get_javascript_expressions_to_filter_extensions_with_operator
(
file_extensions
,
operator
):
return
[
"JSON.stringify(this.contentType).toUpperCase() "
+
operator
+
" JSON.stringify('{}').toUpperCase()"
.
format
(
file_extension
)
for
file_extension
in
file_extensions
]
def
_join_javascript_expressions_for_filters_with_separator
(
javascript_expressions_for_filtering
,
separator
):
return
separator
.
join
(
javascript_expressions_for_filtering
)
def
_get_sort_type_and_direction
(
request_options
):
sort_type
=
_get_mongo_sort_from_requested_sort
(
request_options
[
'requested_sort'
])
sort_direction
=
_get_sort_direction_from_requested_sort
(
request_options
[
'requested_sort_direction'
])
return
[(
sort_type
,
sort_direction
)]
def
_get_mongo_sort_from_requested_sort
(
requested_sort
):
if
requested_sort
==
'date_added'
:
sort
=
'uploadDate'
elif
requested_sort
==
'display_name'
:
sort
=
'displayname'
else
:
sort
=
requested_sort
return
sort
def
_get_sort_direction_from_requested_sort
(
requested_sort_direction
):
if
requested_sort_direction
.
lower
()
==
'asc'
:
return
ASCENDING
return
DESCENDING
def
_get_assets_for_page
(
request
,
course_key
,
options
):
"""
def
_get_current_page
(
requested_page
):
Returns the list of assets for the specified page and page size.
return
max
(
requested_page
,
0
)
"""
def
_get_first_asset_index
(
current_page
,
page_size
):
return
current_page
*
page_size
def
_get_assets_for_page
(
course_key
,
options
):
current_page
=
options
[
'current_page'
]
current_page
=
options
[
'current_page'
]
page_size
=
options
[
'page_size'
]
page_size
=
options
[
'page_size'
]
sort
=
options
[
'sort'
]
sort
=
options
[
'sort'
]
...
@@ -197,50 +311,54 @@ def _get_assets_for_page(request, course_key, options):
...
@@ -197,50 +311,54 @@ def _get_assets_for_page(request, course_key, options):
)
)
def
get_file_size
(
upload_file
):
def
_update_options_to_requery_final_page
(
query_options
,
total_asset_count
):
"""
query_options
[
'current_page'
]
=
int
(
math
.
floor
((
total_asset_count
-
1
)
/
query_options
[
'page_size'
]))
Helper method for getting file size of an upload file.
Can be used for mocking test file sizes.
"""
return
upload_file
.
size
def
update_course_run_asset
(
course_key
,
upload_file
):
def
_get_assets_in_json_format
(
assets
,
course_key
):
filename
=
upload_file
.
name
assets_in_json_format
=
[]
mime_type
=
upload_file
.
content_type
for
asset
in
assets
:
size
=
get_file_size
(
upload_file
)
thumbnail_asset_key
=
_get_thumbnail_asset_key
(
asset
,
course_key
)
asset_is_locked
=
asset
.
get
(
'locked'
,
False
)
max_size_in_mb
=
settings
.
MAX_ASSET_UPLOAD_FILE_SIZE_IN_MB
max_file_size_in_bytes
=
max_size_in_mb
*
1000
**
2
asset_in_json
=
_get_asset_json
(
if
size
>
max_file_size_in_bytes
:
asset
[
'displayname'
],
msg
=
'File {filename} exceeds the maximum size of {max_size_in_mb} MB.'
.
format
(
asset
[
'contentType'
],
filename
=
filename
,
asset
[
'uploadDate'
],
max_size_in_mb
=
max_size_in_mb
asset
[
'asset_key'
],
thumbnail_asset_key
,
asset_is_locked
)
)
raise
AssetSizeTooLargeException
(
msg
)
content_loc
=
StaticContent
.
compute_location
(
course_key
,
filename
)
assets_in_json_format
.
append
(
asset_in_json
)
chunked
=
upload_file
.
multiple_chunks
()
return
assets_in_json_format
sc_partial
=
partial
(
StaticContent
,
content_loc
,
filename
,
mime_type
)
if
chunked
:
content
=
sc_partial
(
upload_file
.
chunks
())
def
update_course_run_asset
(
course_key
,
upload_file
):
tempfile_path
=
upload_file
.
temporary_file_path
()
course_exists_response
=
_get_error_if_course_does_not_exist
(
course_key
)
else
:
content
=
sc_partial
(
upload_file
.
read
())
if
course_exists_response
is
not
None
:
tempfile_path
=
None
return
course_exists_response
file_metadata
=
_get_file_metadata_as_dictionary
(
upload_file
)
is_file_too_large
=
_check_file_size_is_too_large
(
file_metadata
)
if
is_file_too_large
:
error_message
=
_get_file_too_large_error_message
(
file_metadata
[
'filename'
])
raise
AssetSizeTooLargeException
(
error_message
)
# Verify a thumbnail can be created
content
,
temporary_file_path
=
_get_file_content_and_path
(
file_metadata
,
course_key
)
(
thumbnail_content
,
thumbnail_location
)
=
contentstore
()
.
generate_thumbnail
(
content
,
tempfile_path
=
tempfile_path
)
(
thumbnail_content
,
thumbnail_location
)
=
contentstore
()
.
generate_thumbnail
(
content
,
tempfile_path
=
temporary_file_path
)
# delete cached thumbnail even if one couldn't be created this time (else the old thumbnail will continue to show)
# delete cached thumbnail even if one couldn't be created this time (else the old thumbnail will continue to show)
del_cached_content
(
thumbnail_location
)
del_cached_content
(
thumbnail_location
)
# now store thumbnail location only if we could create it
if
_check_thumbnail_uploaded
(
thumbnail_content
):
if
thumbnail_content
is
not
None
:
content
.
thumbnail_location
=
thumbnail_location
content
.
thumbnail_location
=
thumbnail_location
# then commit the content
contentstore
()
.
save
(
content
)
contentstore
()
.
save
(
content
)
del_cached_content
(
content
.
location
)
del_cached_content
(
content
.
location
)
...
@@ -251,18 +369,10 @@ def update_course_run_asset(course_key, upload_file):
...
@@ -251,18 +369,10 @@ def update_course_run_asset(course_key, upload_file):
@ensure_csrf_cookie
@ensure_csrf_cookie
@login_required
@login_required
def
_upload_asset
(
request
,
course_key
):
def
_upload_asset
(
request
,
course_key
):
"""
course_exists_error
=
_get_error_if_course_does_not_exist
(
course_key
)
This method allows for POST uploading of files into the course asset
library, which will be supported by GridFS in MongoDB.
if
course_exists_error
is
not
None
:
"""
return
course_exists_error
# Does the course actually exist?!? Get anything from it to prove its
# existence
try
:
modulestore
()
.
get_course
(
course_key
)
except
ItemNotFoundError
:
# no return it as a Bad Request response
logging
.
error
(
"Could not find course:
%
s"
,
course_key
)
return
HttpResponseBadRequest
()
# compute a 'filename' which is similar to the location formatting, we're
# compute a 'filename' which is similar to the location formatting, we're
# using the 'filename' nomenclature since we're using a FileSystem paradigm
# using the 'filename' nomenclature since we're using a FileSystem paradigm
...
@@ -272,8 +382,8 @@ def _upload_asset(request, course_key):
...
@@ -272,8 +382,8 @@ def _upload_asset(request, course_key):
try
:
try
:
content
=
update_course_run_asset
(
course_key
,
upload_file
)
content
=
update_course_run_asset
(
course_key
,
upload_file
)
except
AssetSizeTooLargeException
as
ex
:
except
AssetSizeTooLargeException
as
ex
ception
:
return
JsonResponse
({
'error'
:
ex
.
message
},
status
=
413
)
return
JsonResponse
({
'error'
:
ex
ception
.
message
},
status
=
413
)
# readback the saved content - we need the database timestamp
# readback the saved content - we need the database timestamp
readback
=
contentstore
()
.
find
(
content
.
location
)
readback
=
contentstore
()
.
find
(
content
.
location
)
...
@@ -291,16 +401,93 @@ def _upload_asset(request, course_key):
...
@@ -291,16 +401,93 @@ def _upload_asset(request, course_key):
})
})
@require_http_methods
((
"DELETE"
,
"POST"
,
"PUT"
))
def
_get_error_if_course_does_not_exist
(
course_key
):
try
:
modulestore
()
.
get_course
(
course_key
)
except
ItemNotFoundError
:
logging
.
error
(
'Could not find course:
%
s'
,
course_key
)
return
HttpResponseBadRequest
()
def
_get_file_metadata_as_dictionary
(
upload_file
):
# compute a 'filename' which is similar to the location formatting; we're
# using the 'filename' nomenclature since we're using a FileSystem paradigm
# here; we're just imposing the Location string formatting expectations to
# keep things a bit more consistent
return
{
'upload_file'
:
upload_file
,
'filename'
:
upload_file
.
name
,
'mime_type'
:
upload_file
.
content_type
,
'upload_file_size'
:
get_file_size
(
upload_file
)
}
def
get_file_size
(
upload_file
):
# can be used for mocking test file sizes.
return
upload_file
.
size
def
_check_file_size_is_too_large
(
file_metadata
):
upload_file_size
=
file_metadata
[
'upload_file_size'
]
maximum_file_size_in_megabytes
=
settings
.
MAX_ASSET_UPLOAD_FILE_SIZE_IN_MB
maximum_file_size_in_bytes
=
maximum_file_size_in_megabytes
*
1000
**
2
return
upload_file_size
>
maximum_file_size_in_bytes
def
_get_file_too_large_error_message
(
filename
):
return
_
(
'File {filename} exceeds maximum size of '
'{maximum_size_in_megabytes} MB.'
)
.
format
(
filename
=
filename
,
maximum_size_in_megabytes
=
settings
.
MAX_ASSET_UPLOAD_FILE_SIZE_IN_MB
,
)
def
_get_file_content_and_path
(
file_metadata
,
course_key
):
content_location
=
StaticContent
.
compute_location
(
course_key
,
file_metadata
[
'filename'
])
upload_file
=
file_metadata
[
'upload_file'
]
file_can_be_chunked
=
upload_file
.
multiple_chunks
()
static_content_partial
=
partial
(
StaticContent
,
content_location
,
file_metadata
[
'filename'
],
file_metadata
[
'mime_type'
])
if
file_can_be_chunked
:
content
=
static_content_partial
(
upload_file
.
chunks
())
temporary_file_path
=
upload_file
.
temporary_file_path
()
else
:
content
=
static_content_partial
(
upload_file
.
read
())
temporary_file_path
=
None
return
content
,
temporary_file_path
def
_check_thumbnail_uploaded
(
thumbnail_content
):
return
thumbnail_content
is
not
None
def
_get_thumbnail_asset_key
(
asset
,
course_key
):
# note, due to the schema change we may not have a 'thumbnail_location' in the result set
thumbnail_location
=
asset
.
get
(
'thumbnail_location'
,
None
)
thumbnail_asset_key
=
None
if
thumbnail_location
:
thumbnail_path
=
thumbnail_location
[
4
]
thumbnail_asset_key
=
course_key
.
make_asset_key
(
'thumbnail'
,
thumbnail_path
)
return
thumbnail_asset_key
@require_http_methods
((
'DELETE'
,
'POST'
,
'PUT'
))
@login_required
@login_required
@ensure_csrf_cookie
@ensure_csrf_cookie
def
_update_asset
(
request
,
course_key
,
asset_key
):
def
_update_asset
(
request
,
course_key
,
asset_key
):
"""
'''
restful CRUD operations for a course asset.
restful CRUD operations for a course asset.
Currently only DELETE, POST, and PUT methods are implemented.
Currently only DELETE, POST, and PUT methods are implemented.
asset_path_encoding: the odd /c4x/org/course/category/name repr of the asset (used by Backbone as the id)
asset_path_encoding: the odd /c4x/org/course/category/name repr of the asset (used by Backbone as the id)
"""
'''
if
request
.
method
==
'DELETE'
:
if
request
.
method
==
'DELETE'
:
try
:
try
:
delete_asset
(
course_key
,
asset_key
)
delete_asset
(
course_key
,
asset_key
)
...
@@ -311,56 +498,60 @@ def _update_asset(request, course_key, asset_key):
...
@@ -311,56 +498,60 @@ def _update_asset(request, course_key, asset_key):
elif
request
.
method
in
(
'PUT'
,
'POST'
):
elif
request
.
method
in
(
'PUT'
,
'POST'
):
if
'file'
in
request
.
FILES
:
if
'file'
in
request
.
FILES
:
return
_upload_asset
(
request
,
course_key
)
return
_upload_asset
(
request
,
course_key
)
else
:
# Update existing asset
# update existing asset
try
:
try
:
modified_asset
=
json
.
loads
(
request
.
body
)
modified_asset
=
json
.
loads
(
request
.
body
)
except
ValueError
:
except
ValueError
:
return
HttpResponseBadRequest
()
return
HttpResponseBadRequest
()
contentstore
()
.
set_attr
(
asset_key
,
'locked'
,
modified_asset
[
'locked'
])
contentstore
()
.
set_attr
(
asset_key
,
'locked'
,
modified_asset
[
'locked'
])
# Delete the asset from the cache so we check the lock status the next time it is requested.
# delete the asset from the cache so we check the lock status the next time it is requested.
del_cached_content
(
asset_key
)
del_cached_content
(
asset_key
)
return
JsonResponse
(
modified_asset
,
status
=
201
)
return
JsonResponse
(
modified_asset
,
status
=
201
)
def
_save_content_to_trash
(
content
):
contentstore
(
'trashcan'
)
.
save
(
content
)
def
delete_asset
(
course_key
,
asset_key
):
def
delete_asset
(
course_key
,
asset_key
):
"""
content
=
_check_existence_and_get_asset_content
(
asset_key
)
Deletes asset represented by given 'asset_key' in the course represented by given course_key.
"""
_save_content_to_trash
(
content
)
# Make sure the item to delete actually exists.
_delete_thumbnail
(
content
.
thumbnail_location
,
course_key
,
asset_key
)
contentstore
()
.
delete
(
content
.
get_id
())
del_cached_content
(
content
.
location
)
def
_check_existence_and_get_asset_content
(
asset_key
):
try
:
try
:
content
=
contentstore
()
.
find
(
asset_key
)
content
=
contentstore
()
.
find
(
asset_key
)
return
content
except
NotFoundError
:
except
NotFoundError
:
raise
AssetNotFoundException
raise
AssetNotFoundException
# ok, save the content into the trashcan
contentstore
(
'trashcan'
)
.
save
(
content
)
# see if there is a thumbnail as well, if so move that as well
def
_delete_thumbnail
(
thumbnail_location
,
course_key
,
asset_key
):
if
content
.
thumbnail_location
is
not
None
:
if
thumbnail_location
is
not
None
:
# We are ignoring the value of the thumbnail_location-- we only care whether
# We are ignoring the value of the thumbnail_location-- we only care whether
# or not a thumbnail has been stored, and we can now easily create the correct path.
# or not a thumbnail has been stored, and we can now easily create the correct path.
thumbnail_location
=
course_key
.
make_asset_key
(
'thumbnail'
,
asset_key
.
name
)
thumbnail_location
=
course_key
.
make_asset_key
(
'thumbnail'
,
asset_key
.
name
)
try
:
try
:
thumbnail_content
=
contentstore
()
.
find
(
thumbnail_location
)
thumbnail_content
=
contentstore
()
.
find
(
thumbnail_location
)
contentstore
(
'trashcan'
)
.
save
(
thumbnail_content
)
_save_content_to_trash
(
thumbnail_content
)
# hard delete thumbnail from origin
contentstore
()
.
delete
(
thumbnail_content
.
get_id
())
contentstore
()
.
delete
(
thumbnail_content
.
get_id
())
# remove from any caching
del_cached_content
(
thumbnail_location
)
del_cached_content
(
thumbnail_location
)
except
Exception
:
# pylint: disable=broad-except
except
Exception
:
# pylint: disable=broad-except
logging
.
warning
(
'Could not delete thumbnail:
%
s'
,
thumbnail_location
)
logging
.
warning
(
'Could not delete thumbnail:
%
s'
,
thumbnail_location
)
# delete the original
contentstore
()
.
delete
(
content
.
get_id
())
# remove from cache
del_cached_content
(
content
.
location
)
def
_get_asset_json
(
display_name
,
content_type
,
date
,
location
,
thumbnail_location
,
locked
):
def
_get_asset_json
(
display_name
,
content_type
,
date
,
location
,
thumbnail_location
,
locked
):
"""
'''
Helper method for formatting the asset information to send to client.
Helper method for formatting the asset information to send to client.
"""
'''
asset_url
=
StaticContent
.
serialize_asset_key_with_slash
(
location
)
asset_url
=
StaticContent
.
serialize_asset_key_with_slash
(
location
)
external_url
=
settings
.
LMS_BASE
+
asset_url
external_url
=
settings
.
LMS_BASE
+
asset_url
return
{
return
{
...
@@ -372,6 +563,6 @@ def _get_asset_json(display_name, content_type, date, location, thumbnail_locati
...
@@ -372,6 +563,6 @@ def _get_asset_json(display_name, content_type, date, location, thumbnail_locati
'portable_url'
:
StaticContent
.
get_static_path_from_location
(
location
),
'portable_url'
:
StaticContent
.
get_static_path_from_location
(
location
),
'thumbnail'
:
StaticContent
.
serialize_asset_key_with_slash
(
thumbnail_location
)
if
thumbnail_location
else
None
,
'thumbnail'
:
StaticContent
.
serialize_asset_key_with_slash
(
thumbnail_location
)
if
thumbnail_location
else
None
,
'locked'
:
locked
,
'locked'
:
locked
,
#
N
eeded for Backbone delete/update.
#
n
eeded for Backbone delete/update.
'id'
:
unicode
(
location
)
'id'
:
unicode
(
location
)
}
}
cms/djangoapps/contentstore/views/tests/test_assets.py
View file @
282220da
...
@@ -174,6 +174,15 @@ class PaginationTestCase(AssetsTestCase):
...
@@ -174,6 +174,15 @@ class PaginationTestCase(AssetsTestCase):
self
.
assert_correct_filter_response
(
self
.
url
,
'asset_type'
,
'OTHER'
)
self
.
assert_correct_filter_response
(
self
.
url
,
'asset_type'
,
'OTHER'
)
self
.
assert_correct_filter_response
(
self
.
assert_correct_filter_response
(
self
.
url
,
'asset_type'
,
'Documents'
)
self
.
url
,
'asset_type'
,
'Documents'
)
self
.
assert_correct_filter_response
(
self
.
url
,
'asset_type'
,
'Documents,Images'
)
self
.
assert_correct_filter_response
(
self
.
url
,
'asset_type'
,
'Documents,OTHER'
)
#Verify invalid request parameters
self
.
assert_invalid_parameters_error
(
self
.
url
,
'asset_type'
,
'edX'
)
self
.
assert_invalid_parameters_error
(
self
.
url
,
'asset_type'
,
'edX, OTHER'
)
self
.
assert_invalid_parameters_error
(
self
.
url
,
'asset_type'
,
'edX, Images'
)
# Verify querying outside the range of valid pages
# Verify querying outside the range of valid pages
self
.
assert_correct_asset_response
(
self
.
assert_correct_asset_response
(
...
@@ -247,24 +256,46 @@ class PaginationTestCase(AssetsTestCase):
...
@@ -247,24 +256,46 @@ class PaginationTestCase(AssetsTestCase):
"""
"""
Get from the url w/ a filter option and ensure items honor that filter
Get from the url w/ a filter option and ensure items honor that filter
"""
"""
requested_file_types
=
settings
.
FILES_AND_UPLOAD_TYPE_FILTERS
.
get
(
filter_value
,
None
)
filter_value_split
=
filter_value
.
split
(
','
)
requested_file_extensions
=
[]
all_file_extensions
=
[]
for
requested_filter
in
filter_value_split
:
if
requested_filter
==
'OTHER'
:
for
file_type
in
settings
.
FILES_AND_UPLOAD_TYPE_FILTERS
:
all_file_extensions
.
extend
(
file_type
)
else
:
file_extensions
=
settings
.
FILES_AND_UPLOAD_TYPE_FILTERS
.
get
(
requested_filter
,
None
)
if
file_extensions
is
not
None
:
requested_file_extensions
.
extend
(
file_extensions
)
resp
=
self
.
client
.
get
(
resp
=
self
.
client
.
get
(
url
+
'?'
+
filter_type
+
'='
+
filter_value
,
HTTP_ACCEPT
=
'application/json'
)
url
+
'?'
+
filter_type
+
'='
+
filter_value
,
HTTP_ACCEPT
=
'application/json'
)
json_response
=
json
.
loads
(
resp
.
content
)
json_response
=
json
.
loads
(
resp
.
content
)
assets_response
=
json_response
[
'assets'
]
assets_response
=
json_response
[
'assets'
]
if
filter_value
is
not
''
:
if
filter_value
is
not
''
:
content_types
=
[
asset
[
'content_type'
]
.
lower
()
content_types
=
[
asset
[
'content_type'
]
.
lower
()
for
asset
in
assets_response
]
for
asset
in
assets_response
]
if
filter_value
is
'OTHER'
:
if
'OTHER'
in
filter_value_split
:
all_file_type_extensions
=
[]
for
file_type
in
settings
.
FILES_AND_UPLOAD_TYPE_FILTERS
:
all_file_type_extensions
.
extend
(
file_type
)
for
content_type
in
content_types
:
for
content_type
in
content_types
:
self
.
assertNotIn
(
content_type
,
all_file_type_extensions
)
# content_type is either not any defined type (i.e. OTHER) or is a defined type (if multiple
# parameters including OTHER are used)
self
.
assertTrue
(
content_type
in
requested_file_extensions
or
content_type
not
in
all_file_extensions
)
else
:
else
:
for
content_type
in
content_types
:
for
content_type
in
content_types
:
self
.
assertIn
(
content_type
,
requested_file_types
)
self
.
assertIn
(
content_type
,
requested_file_extensions
)
def
assert_invalid_parameters_error
(
self
,
url
,
filter_type
,
filter_value
):
"""
Get from the url w/ invalid filter option(s) and ensure error is received
"""
resp
=
self
.
client
.
get
(
url
+
'?'
+
filter_type
+
'='
+
filter_value
,
HTTP_ACCEPT
=
'application/json'
)
self
.
assertEquals
(
resp
.
status_code
,
400
)
@ddt
@ddt
...
...
cms/envs/common.py
View file @
282220da
...
@@ -1257,7 +1257,8 @@ ADVANCED_PROBLEM_TYPES = [
...
@@ -1257,7 +1257,8 @@ ADVANCED_PROBLEM_TYPES = [
# Files and Uploads type filter values
# Files and Uploads type filter values
FILES_AND_UPLOAD_TYPE_FILTERS
=
{
FILES_AND_UPLOAD_TYPE_FILTERS
=
{
"Images"
:
[
'image/png'
,
'image/jpeg'
,
'image/jpg'
,
'image/gif'
,
'image/tiff'
,
'image/tif'
,
'image/x-icon'
],
"Images"
:
[
'image/png'
,
'image/jpeg'
,
'image/jpg'
,
'image/gif'
,
'image/tiff'
,
'image/tif'
,
'image/x-icon'
,
'image/svg+xml'
,
'image/bmp'
,
'image/x-ms-bmp'
,
],
"Documents"
:
[
"Documents"
:
[
'application/pdf'
,
'application/pdf'
,
'text/plain'
,
'text/plain'
,
...
@@ -1271,7 +1272,25 @@ FILES_AND_UPLOAD_TYPE_FILTERS = {
...
@@ -1271,7 +1272,25 @@ FILES_AND_UPLOAD_TYPE_FILTERS = {
'application/msword'
,
'application/msword'
,
'application/vnd.ms-excel'
,
'application/vnd.ms-excel'
,
'application/vnd.ms-powerpoint'
,
'application/vnd.ms-powerpoint'
,
'application/csv'
,
'application/vnd.ms-excel.sheet.macroEnabled.12'
,
'text/x-tex'
,
'application/x-pdf'
,
'application/vnd.ms-excel.sheet.macroenabled.12'
,
'file/pdf'
,
'image/pdf'
,
'text/csv'
,
'text/pdf'
,
'text/x-sh'
,
'
\a
pplication/pdf
\"
"'
,
],
],
"Audio"
:
[
'audio/mpeg'
,
'audio/mp3'
,
'audio/x-wav'
,
'audio/ogg'
,
'audio/wav'
,
'audio/aac'
,
'audio/x-m4a'
,
'audio/mp4'
,
'audio/x-ms-wma'
,
],
"Code"
:
[
'application/json'
,
'text/html'
,
'text/javascript'
,
'application/javascript'
,
'text/css'
,
'text/x-python'
,
'application/x-java-jnlp-file'
,
'application/xml'
,
'application/postscript'
,
'application/x-javascript'
,
'application/java-vm'
,
'text/x-c++src'
,
'text/xml'
,
'text/x-scss'
,
'application/x-python-code'
,
'application/java-archive'
,
'text/x-python-script'
,
'application/x-ruby'
,
'application/mathematica'
,
'text/coffeescript'
,
'text/x-matlab'
,
'application/sql'
,
'text/php'
,
]
}
}
# Default to no Search Engine
# Default to no Search Engine
...
...
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