Commit 282220da by Michael Roytman

refactor and add multiple filter functionality

parent d62e2498
...@@ -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_parameters
} }
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 exception:
return JsonResponse({'error': ex.message}, status=413) return JsonResponse({'error': exception.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,
# Needed for Backbone delete/update. # needed for Backbone delete/update.
'id': unicode(location) 'id': unicode(location)
} }
...@@ -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
......
...@@ -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',
'\application/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
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment