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
__all__
=
[
'assets_handler'
]
# pylint: disable=unused-argument
REQUEST_DEFAULTS
=
{
'page'
:
0
,
'page_size'
:
50
,
'sort'
:
'date_added'
,
'direction'
:
''
,
'asset_type'
:
''
}
@login_required
@ensure_csrf_cookie
def
assets_handler
(
request
,
course_key_string
=
None
,
asset_key_string
=
None
):
"""
'''
The restful handler for 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
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):
json: returns a page of assets. The following parameters are supported:
page: the desired page of results (defaults to 0)
page_size: the number of items per page (defaults to 50)
sort: the asset field to sort by (defaults to
"date_added"
)
direction: the sort direction (defaults to
"descending"
)
sort: the asset field to sort by (defaults to
'date_added'
)
direction: the sort direction (defaults to
'descending'
)
POST
json: create (or update?) an asset. The only updating that can be done is changing the lock state.
PUT
json: update the locked state of an asset
DELETE
json: delete an asset
"""
'''
course_key
=
CourseKey
.
from_string
(
course_key_string
)
if
not
has_course_author_access
(
request
.
user
,
course_key
):
raise
PermissionDenied
()
response_format
=
request
.
GET
.
get
(
'format'
)
or
request
.
POST
.
get
(
'format'
)
or
'html'
if
response_format
==
'json'
or
'application/json'
in
request
.
META
.
get
(
'HTTP_ACCEPT'
,
'application/json'
):
response_format
=
_get_response_format
(
request
)
if
_request_response_format_is_json
(
request
,
response_format
):
if
request
.
method
==
'GET'
:
return
_assets_json
(
request
,
course_key
)
else
:
asset_key
=
AssetKey
.
from_string
(
asset_key_string
)
if
asset_key_string
else
None
return
_update_asset
(
request
,
course_key
,
asset_key
)
asset_key
=
AssetKey
.
from_string
(
asset_key_string
)
if
asset_key_string
else
None
return
_update_asset
(
request
,
course_key
,
asset_key
)
elif
request
.
method
==
'GET'
:
# assume html
return
_asset_index
(
request
,
course_key
)
else
:
return
HttpResponseNotFound
()
return
_asset_index
(
course_key
)
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.
Supports start (0-based index into the list of assets) and max query parameters.
"""
'''
course_module
=
modulestore
()
.
get_course
(
course_key
)
return
render_to_response
(
'asset_index.html'
,
{
...
...
@@ -90,102 +104,202 @@ def _asset_index(request, course_key):
def
_assets_json
(
request
,
course_key
):
"""
'''
Display an editable asset library.
Supports start (0-based index into the list of assets) and max query parameters.
"""
requested_page
=
int
(
request
.
GET
.
get
(
'page'
,
0
))
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
),
}
'''
request_options
=
_parse_request_to_dictionary
(
request
)
sort_direction
=
DESCENDING
if
request
.
GET
.
get
(
'direction'
,
''
)
.
lower
()
==
'asc'
:
sort_direction
=
ASCENDING
filter_parameters
=
None
# Convert the field name to the Mongo name
if
requested_sort
==
'date_added'
:
requested_sort
=
'uploadDate'
elif
requested_sort
==
'display_name'
:
requested_sort
=
'displayname'
sort
=
[(
requested_sort
,
sort_direction
)]
if
request_options
[
'requested_asset_type'
]:
filters_are_invalid_error
=
_get_error_if_invalid_parameters
(
request_options
[
'requested_asset_type'
])
if
filters_are_invalid_error
is
not
None
:
return
filters_are_invalid_error
filter_parameters
=
_get_filter_parameters_for_mongo
(
request_options
[
'requested_asset_type'
])
current_page
=
max
(
requested_page
,
0
)
start
=
current_page
*
requested_page_size
options
=
{
sort_type_and_direction
=
_get_sort_type_and_direction
(
request_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
,
'page_size'
:
requested_page_size
,
'sort'
:
sort
,
'filter_params'
:
filter_params
'sort'
:
sort
_type_and_direction
,
'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
({
'start'
:
start
,
'end'
:
end
,
assets
,
total_count
=
_get_assets_for_page
(
course_key
,
query_options
)
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
,
'pageSize'
:
requested_page_size
,
'totalCount'
:
total_count
,
'assets'
:
asset_json
,
'sort'
:
requested_sort
,
})
'assets'
:
assets_in_json_format
,
'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
):
"""
Returns the list of assets for the specified page and page size.
"""
def
_get_current_page
(
requested_page
):
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'
]
page_size
=
options
[
'page_size'
]
sort
=
options
[
'sort'
]
...
...
@@ -197,50 +311,54 @@ def _get_assets_for_page(request, course_key, options):
)
def
get_file_size
(
upload_file
):
"""
Helper method for getting file size of an upload file.
Can be used for mocking test file sizes.
"""
return
upload_file
.
size
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'
]))
def
update_course_run_asset
(
course_key
,
upload_file
):
filename
=
upload_file
.
name
mime_type
=
upload_file
.
content_type
size
=
get_file_size
(
upload_file
)
max_size_in_mb
=
settings
.
MAX_ASSET_UPLOAD_FILE_SIZE_IN_MB
max_file_size_in_bytes
=
max_size_in_mb
*
1000
**
2
if
size
>
max_file_size_in_bytes
:
msg
=
'File {filename} exceeds the maximum size of {max_size_in_mb} MB.'
.
format
(
filename
=
filename
,
max_size_in_mb
=
max_size_in_mb
def
_get_assets_in_json_format
(
assets
,
course_key
):
assets_in_json_format
=
[]
for
asset
in
assets
:
thumbnail_asset_key
=
_get_thumbnail_asset_key
(
asset
,
course_key
)
asset_is_locked
=
asset
.
get
(
'locked'
,
False
)
asset_in_json
=
_get_asset_json
(
asset
[
'displayname'
],
asset
[
'contentType'
],
asset
[
'uploadDate'
],
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
()
sc_partial
=
partial
(
StaticContent
,
content_loc
,
filename
,
mime_type
)
if
chunked
:
content
=
sc_partial
(
upload_file
.
chunks
())
tempfile_path
=
upload_file
.
temporary_file_path
()
else
:
content
=
sc_partial
(
upload_file
.
read
())
tempfile_path
=
None
return
assets_in_json_format
def
update_course_run_asset
(
course_key
,
upload_file
):
course_exists_response
=
_get_error_if_course_does_not_exist
(
course_key
)
if
course_exists_response
is
not
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
(
thumbnail_content
,
thumbnail_location
)
=
contentstore
()
.
generate_thumbnail
(
content
,
tempfile_path
=
tempfile_path
)
content
,
temporary_file_path
=
_get_file_content_and_path
(
file_metadata
,
course_key
)
(
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)
del_cached_content
(
thumbnail_location
)
# now store thumbnail location only if we could create it
if
thumbnail_content
is
not
None
:
if
_check_thumbnail_uploaded
(
thumbnail_content
):
content
.
thumbnail_location
=
thumbnail_location
# then commit the content
contentstore
()
.
save
(
content
)
del_cached_content
(
content
.
location
)
...
...
@@ -251,18 +369,10 @@ def update_course_run_asset(course_key, upload_file):
@ensure_csrf_cookie
@login_required
def
_upload_asset
(
request
,
course_key
):
"""
This method allows for POST uploading of files into the course asset
library, which will be supported by GridFS in MongoDB.
"""
# 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
()
course_exists_error
=
_get_error_if_course_does_not_exist
(
course_key
)
if
course_exists_error
is
not
None
:
return
course_exists_error
# compute a 'filename' which is similar to the location formatting, we're
# using the 'filename' nomenclature since we're using a FileSystem paradigm
...
...
@@ -272,8 +382,8 @@ def _upload_asset(request, course_key):
try
:
content
=
update_course_run_asset
(
course_key
,
upload_file
)
except
AssetSizeTooLargeException
as
ex
:
return
JsonResponse
({
'error'
:
ex
.
message
},
status
=
413
)
except
AssetSizeTooLargeException
as
ex
ception
:
return
JsonResponse
({
'error'
:
ex
ception
.
message
},
status
=
413
)
# readback the saved content - we need the database timestamp
readback
=
contentstore
()
.
find
(
content
.
location
)
...
...
@@ -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
@ensure_csrf_cookie
def
_update_asset
(
request
,
course_key
,
asset_key
):
"""
'''
restful CRUD operations for a course asset.
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)
"""
'''
if
request
.
method
==
'DELETE'
:
try
:
delete_asset
(
course_key
,
asset_key
)
...
...
@@ -311,56 +498,60 @@ def _update_asset(request, course_key, asset_key):
elif
request
.
method
in
(
'PUT'
,
'POST'
):
if
'file'
in
request
.
FILES
:
return
_upload_asset
(
request
,
course_key
)
else
:
# Update existing asset
try
:
modified_asset
=
json
.
loads
(
request
.
body
)
except
ValueError
:
return
HttpResponseBadRequest
()
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.
del_cached_content
(
asset_key
)
return
JsonResponse
(
modified_asset
,
status
=
201
)
# update existing asset
try
:
modified_asset
=
json
.
loads
(
request
.
body
)
except
ValueError
:
return
HttpResponseBadRequest
()
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.
del_cached_content
(
asset_key
)
return
JsonResponse
(
modified_asset
,
status
=
201
)
def
_save_content_to_trash
(
content
):
contentstore
(
'trashcan'
)
.
save
(
content
)
def
delete_asset
(
course_key
,
asset_key
):
"""
Deletes asset represented by given 'asset_key' in the course represented by given course_key.
"""
# Make sure the item to delete actually exists.
content
=
_check_existence_and_get_asset_content
(
asset_key
)
_save_content_to_trash
(
content
)
_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
:
content
=
contentstore
()
.
find
(
asset_key
)
return
content
except
NotFoundError
:
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
if
content
.
thumbnail_location
is
not
None
:
def
_delete_thumbnail
(
thumbnail_location
,
course_key
,
asset_key
):
if
thumbnail_location
is
not
None
:
# 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.
thumbnail_location
=
course_key
.
make_asset_key
(
'thumbnail'
,
asset_key
.
name
)
try
:
thumbnail_content
=
contentstore
()
.
find
(
thumbnail_location
)
contentstore
(
'trashcan'
)
.
save
(
thumbnail_content
)
# hard delete thumbnail from origin
_save_content_to_trash
(
thumbnail_content
)
contentstore
()
.
delete
(
thumbnail_content
.
get_id
())
# remove from any caching
del_cached_content
(
thumbnail_location
)
except
Exception
:
# pylint: disable=broad-except
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
):
"""
'''
Helper method for formatting the asset information to send to client.
"""
'''
asset_url
=
StaticContent
.
serialize_asset_key_with_slash
(
location
)
external_url
=
settings
.
LMS_BASE
+
asset_url
return
{
...
...
@@ -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
),
'thumbnail'
:
StaticContent
.
serialize_asset_key_with_slash
(
thumbnail_location
)
if
thumbnail_location
else
None
,
'locked'
:
locked
,
#
N
eeded for Backbone delete/update.
#
n
eeded for Backbone delete/update.
'id'
:
unicode
(
location
)
}
cms/djangoapps/contentstore/views/tests/test_assets.py
View file @
282220da
...
...
@@ -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'
,
'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
self
.
assert_correct_asset_response
(
...
...
@@ -247,24 +256,46 @@ class PaginationTestCase(AssetsTestCase):
"""
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
(
url
+
'?'
+
filter_type
+
'='
+
filter_value
,
HTTP_ACCEPT
=
'application/json'
)
json_response
=
json
.
loads
(
resp
.
content
)
assets_response
=
json_response
[
'assets'
]
if
filter_value
is
not
''
:
content_types
=
[
asset
[
'content_type'
]
.
lower
()
for
asset
in
assets_response
]
if
filter_value
is
'OTHER'
:
all_file_type_extensions
=
[]
for
file_type
in
settings
.
FILES_AND_UPLOAD_TYPE_FILTERS
:
all_file_type_extensions
.
extend
(
file_type
)
if
'OTHER'
in
filter_value_split
:
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
:
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
...
...
cms/envs/common.py
View file @
282220da
...
...
@@ -1257,7 +1257,8 @@ ADVANCED_PROBLEM_TYPES = [
# Files and Uploads type filter values
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"
:
[
'application/pdf'
,
'text/plain'
,
...
...
@@ -1271,7 +1272,25 @@ FILES_AND_UPLOAD_TYPE_FILTERS = {
'application/msword'
,
'application/vnd.ms-excel'
,
'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
...
...
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