Implements SOL-20. Filtering for assets table by asset type
This commit is contained in:
@@ -31,8 +31,9 @@ from xmodule.modulestore.exceptions import ItemNotFoundError
|
||||
|
||||
__all__ = ['assets_handler']
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
|
||||
|
||||
@login_required
|
||||
@ensure_csrf_cookie
|
||||
def assets_handler(request, course_key_string=None, asset_key_string=None):
|
||||
@@ -99,6 +100,29 @@ def _assets_json(request, course_key):
|
||||
requested_page = int(request.REQUEST.get('page', 0))
|
||||
requested_page_size = int(request.REQUEST.get('page_size', 50))
|
||||
requested_sort = request.REQUEST.get('sort', 'date_added')
|
||||
requested_filter = request.REQUEST.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
|
||||
if request.REQUEST.get('direction', '').lower() == 'asc':
|
||||
sort_direction = ASCENDING
|
||||
@@ -112,26 +136,42 @@ def _assets_json(request, course_key):
|
||||
|
||||
current_page = max(requested_page, 0)
|
||||
start = current_page * requested_page_size
|
||||
assets, total_count = _get_assets_for_page(request, course_key, current_page, requested_page_size, sort)
|
||||
options = {
|
||||
'current_page': current_page,
|
||||
'page_size': requested_page_size,
|
||||
'sort': sort,
|
||||
'filter_params': filter_params
|
||||
}
|
||||
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 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:
|
||||
current_page = int(math.floor((total_count - 1) / requested_page_size))
|
||||
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, current_page, requested_page_size, sort)
|
||||
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
|
||||
# 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])
|
||||
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['uploadDate'], asset_location, thumbnail_location, asset_locked))
|
||||
asset_json.append(_get_asset_json(
|
||||
asset['displayname'],
|
||||
asset['contentType'],
|
||||
asset['uploadDate'],
|
||||
asset_location,
|
||||
thumbnail_location,
|
||||
asset_locked
|
||||
))
|
||||
|
||||
return JsonResponse({
|
||||
'start': start,
|
||||
@@ -144,14 +184,18 @@ def _assets_json(request, course_key):
|
||||
})
|
||||
|
||||
|
||||
def _get_assets_for_page(request, course_key, current_page, page_size, sort):
|
||||
def _get_assets_for_page(request, course_key, options):
|
||||
"""
|
||||
Returns the list of assets for the specified page and page size.
|
||||
"""
|
||||
current_page = options['current_page']
|
||||
page_size = options['page_size']
|
||||
sort = options['sort']
|
||||
filter_params = options['filter_params'] if options['filter_params'] else None
|
||||
start = current_page * page_size
|
||||
|
||||
return contentstore().get_all_content_for_course(
|
||||
course_key, start=start, maxresults=page_size, sort=sort
|
||||
course_key, start=start, maxresults=page_size, sort=sort, filter_params=filter_params
|
||||
)
|
||||
|
||||
|
||||
@@ -239,10 +283,16 @@ def _upload_asset(request, course_key):
|
||||
|
||||
# readback the saved content - we need the database timestamp
|
||||
readback = contentstore().find(content.location)
|
||||
|
||||
locked = getattr(content, 'locked', False)
|
||||
response_payload = {
|
||||
'asset': _get_asset_json(content.name, readback.last_modified_at, content.location, content.thumbnail_location, locked),
|
||||
'asset': _get_asset_json(
|
||||
content.name,
|
||||
content.content_type,
|
||||
readback.last_modified_at,
|
||||
content.location,
|
||||
content.thumbnail_location,
|
||||
locked
|
||||
),
|
||||
'msg': _('Upload completed')
|
||||
}
|
||||
|
||||
@@ -305,7 +355,7 @@ def _update_asset(request, course_key, asset_key):
|
||||
return JsonResponse(modified_asset, status=201)
|
||||
|
||||
|
||||
def _get_asset_json(display_name, 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.
|
||||
"""
|
||||
@@ -313,6 +363,7 @@ def _get_asset_json(display_name, date, location, thumbnail_location, locked):
|
||||
external_url = settings.LMS_BASE + asset_url
|
||||
return {
|
||||
'display_name': display_name,
|
||||
'content_type': content_type,
|
||||
'date_added': get_default_time_display(date),
|
||||
'url': asset_url,
|
||||
'external_url': external_url,
|
||||
|
||||
@@ -34,17 +34,17 @@ class AssetsTestCase(CourseTestCase):
|
||||
super(AssetsTestCase, self).setUp()
|
||||
self.url = reverse_course_url('assets_handler', self.course.id)
|
||||
|
||||
def upload_asset(self, name="asset-1"):
|
||||
def upload_asset(self, name="asset-1", extension=".txt"):
|
||||
"""
|
||||
Post to the asset upload url
|
||||
"""
|
||||
f = self.get_sample_asset(name)
|
||||
f = self.get_sample_asset(name, extension)
|
||||
return self.client.post(self.url, {"name": name, "file": f})
|
||||
|
||||
def get_sample_asset(self, name):
|
||||
def get_sample_asset(self, name, extension=".txt"):
|
||||
"""Returns an in-memory file with the given name for testing"""
|
||||
f = BytesIO(name)
|
||||
f.name = name + ".txt"
|
||||
f.name = name + extension
|
||||
return f
|
||||
|
||||
|
||||
@@ -98,20 +98,60 @@ class PaginationTestCase(AssetsTestCase):
|
||||
self.upload_asset("asset-1")
|
||||
self.upload_asset("asset-2")
|
||||
self.upload_asset("asset-3")
|
||||
self.upload_asset("asset-4", ".odt")
|
||||
|
||||
# Verify valid page requests
|
||||
self.assert_correct_asset_response(self.url, 0, 3, 3)
|
||||
self.assert_correct_asset_response(self.url + "?page_size=2", 0, 2, 3)
|
||||
self.assert_correct_asset_response(self.url + "?page_size=2&page=1", 2, 1, 3)
|
||||
self.assert_correct_asset_response(self.url, 0, 4, 4)
|
||||
self.assert_correct_asset_response(self.url + "?page_size=2", 0, 2, 4)
|
||||
self.assert_correct_asset_response(
|
||||
self.url + "?page_size=2&page=1", 2, 2, 4)
|
||||
self.assert_correct_sort_response(self.url, 'date_added', 'asc')
|
||||
self.assert_correct_sort_response(self.url, 'date_added', 'desc')
|
||||
self.assert_correct_sort_response(self.url, 'display_name', 'asc')
|
||||
self.assert_correct_sort_response(self.url, 'display_name', 'desc')
|
||||
self.assert_correct_filter_response(self.url, 'asset_type', '')
|
||||
self.assert_correct_filter_response(self.url, 'asset_type', 'OTHER')
|
||||
self.assert_correct_filter_response(
|
||||
self.url, 'asset_type', 'Documents')
|
||||
|
||||
# Verify querying outside the range of valid pages
|
||||
self.assert_correct_asset_response(self.url + "?page_size=2&page=-1", 0, 2, 3)
|
||||
self.assert_correct_asset_response(self.url + "?page_size=2&page=2", 2, 1, 3)
|
||||
self.assert_correct_asset_response(self.url + "?page_size=3&page=1", 0, 3, 3)
|
||||
self.assert_correct_asset_response(
|
||||
self.url + "?page_size=2&page=-1", 0, 2, 4)
|
||||
self.assert_correct_asset_response(
|
||||
self.url + "?page_size=2&page=2", 2, 2, 4)
|
||||
self.assert_correct_asset_response(
|
||||
self.url + "?page_size=3&page=1", 3, 1, 4)
|
||||
|
||||
@mock.patch('xmodule.contentstore.mongo.MongoContentStore.get_all_content_for_course')
|
||||
def test_mocked_filtered_response(self, mock_get_all_content_for_course):
|
||||
"""
|
||||
Test the ajax asset interfaces
|
||||
"""
|
||||
asset_key = self.course.id.make_asset_key(
|
||||
AssetMetadata.GENERAL_ASSET_TYPE, 'test.jpg')
|
||||
upload_date = datetime(2015, 1, 12, 10, 30, tzinfo=UTC)
|
||||
thumbnail_location = [
|
||||
'c4x', 'edX', 'toy', 'thumbnail', 'test_thumb.jpg', None]
|
||||
|
||||
mock_get_all_content_for_course.return_value = [
|
||||
[
|
||||
{
|
||||
"asset_key": asset_key,
|
||||
"displayname": "test.jpg",
|
||||
"contentType": "image/jpg",
|
||||
"url": "/c4x/A/CS102/asset/test.jpg",
|
||||
"uploadDate": upload_date,
|
||||
"id": "/c4x/A/CS102/asset/test.jpg",
|
||||
"portable_url": "/static/test.jpg",
|
||||
"thumbnail": None,
|
||||
"thumbnail_location": thumbnail_location,
|
||||
"locked": None
|
||||
}
|
||||
],
|
||||
1
|
||||
]
|
||||
# Verify valid page requests
|
||||
self.assert_correct_filter_response(self.url, 'asset_type', 'OTHER')
|
||||
|
||||
def assert_correct_asset_response(self, url, expected_start, expected_length, expected_total):
|
||||
"""
|
||||
@@ -128,7 +168,8 @@ class PaginationTestCase(AssetsTestCase):
|
||||
"""
|
||||
Get from the url w/ a sort option and ensure items honor that sort
|
||||
"""
|
||||
resp = self.client.get(url + '?sort=' + sort + '&direction=' + direction, HTTP_ACCEPT='application/json')
|
||||
resp = self.client.get(
|
||||
url + '?sort=' + sort + '&direction=' + direction, HTTP_ACCEPT='application/json')
|
||||
json_response = json.loads(resp.content)
|
||||
assets_response = json_response['assets']
|
||||
name1 = assets_response[0][sort]
|
||||
@@ -141,6 +182,29 @@ class PaginationTestCase(AssetsTestCase):
|
||||
self.assertGreaterEqual(name1, name2)
|
||||
self.assertGreaterEqual(name2, name3)
|
||||
|
||||
def assert_correct_filter_response(self, url, filter_type, filter_value):
|
||||
"""
|
||||
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)
|
||||
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)
|
||||
for content_type in content_types:
|
||||
self.assertNotIn(content_type, all_file_type_extensions)
|
||||
else:
|
||||
for content_type in content_types:
|
||||
self.assertIn(content_type, requested_file_types)
|
||||
|
||||
|
||||
@ddt
|
||||
class UploadTestCase(AssetsTestCase):
|
||||
@@ -229,13 +293,13 @@ class AssetToJsonTestCase(AssetsTestCase):
|
||||
@override_settings(LMS_BASE="lms_base_url")
|
||||
def test_basic(self):
|
||||
upload_date = datetime(2013, 6, 1, 10, 30, tzinfo=UTC)
|
||||
|
||||
content_type = 'image/jpg'
|
||||
course_key = SlashSeparatedCourseKey('org', 'class', 'run')
|
||||
location = course_key.make_asset_key('asset', 'my_file_name.jpg')
|
||||
thumbnail_location = course_key.make_asset_key('thumbnail', 'my_file_name_thumb.jpg')
|
||||
|
||||
# pylint: disable=protected-access
|
||||
output = assets._get_asset_json("my_file", upload_date, location, thumbnail_location, True)
|
||||
output = assets._get_asset_json("my_file", content_type, upload_date, location, thumbnail_location, True)
|
||||
|
||||
self.assertEquals(output["display_name"], "my_file")
|
||||
self.assertEquals(output["date_added"], "Jun 01, 2013 at 10:30 UTC")
|
||||
@@ -246,7 +310,7 @@ class AssetToJsonTestCase(AssetsTestCase):
|
||||
self.assertEquals(output["id"], unicode(location))
|
||||
self.assertEquals(output['locked'], True)
|
||||
|
||||
output = assets._get_asset_json("name", upload_date, location, None, False)
|
||||
output = assets._get_asset_json("name", content_type, upload_date, location, None, False)
|
||||
self.assertIsNone(output["thumbnail"])
|
||||
|
||||
|
||||
@@ -267,6 +331,7 @@ class LockAssetTestCase(AssetsTestCase):
|
||||
|
||||
def post_asset_update(lock, course):
|
||||
""" Helper method for posting asset update. """
|
||||
content_type = 'application/txt'
|
||||
upload_date = datetime(2013, 6, 1, 10, 30, tzinfo=UTC)
|
||||
asset_location = course.id.make_asset_key('asset', 'sample_static.txt')
|
||||
url = reverse_course_url('assets_handler', course.id, kwargs={'asset_key_string': unicode(asset_location)})
|
||||
@@ -274,9 +339,11 @@ class LockAssetTestCase(AssetsTestCase):
|
||||
resp = self.client.post(
|
||||
url,
|
||||
# pylint: disable=protected-access
|
||||
json.dumps(assets._get_asset_json("sample_static.txt", upload_date, asset_location, None, lock)),
|
||||
json.dumps(assets._get_asset_json(
|
||||
"sample_static.txt", content_type, upload_date, asset_location, None, lock)),
|
||||
"application/json"
|
||||
)
|
||||
|
||||
self.assertEqual(resp.status_code, 201)
|
||||
return json.loads(resp.content)
|
||||
|
||||
|
||||
@@ -820,5 +820,26 @@ ADVANCED_PROBLEM_TYPES = [
|
||||
'boilerplate_name': None,
|
||||
}
|
||||
]
|
||||
|
||||
#date format the api will be formatting the datetime values
|
||||
API_DATE_FORMAT = '%Y-%m-%d'
|
||||
|
||||
# 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'],
|
||||
"Documents": [
|
||||
'application/pdf',
|
||||
'text/plain',
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
|
||||
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
||||
'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
|
||||
'application/vnd.openxmlformats-officedocument.presentationml.template',
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
|
||||
'application/msword',
|
||||
'application/vnd.ms-excel',
|
||||
'application/vnd.ms-powerpoint',
|
||||
],
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ domReady(function() {
|
||||
$('.nav-dd .nav-item .title').removeClass('is-selected');
|
||||
});
|
||||
|
||||
$('.nav-dd .nav-item').click(function(e) {
|
||||
$('.nav-dd .nav-item, .filterable-column .nav-item').click(function(e) {
|
||||
|
||||
$subnav = $(this).find('.wrapper-nav-sub');
|
||||
$title = $(this).find('.title');
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
define(["backbone.paginator", "js/models/asset"], function(BackbonePaginator, AssetModel) {
|
||||
var AssetCollection = BackbonePaginator.requestPager.extend({
|
||||
assetType: '',
|
||||
model : AssetModel,
|
||||
paginator_core: {
|
||||
type: 'GET',
|
||||
@@ -17,6 +18,7 @@ define(["backbone.paginator", "js/models/asset"], function(BackbonePaginator, As
|
||||
'page_size': function() { return this.perPage; },
|
||||
'sort': function() { return this.sortField; },
|
||||
'direction': function() { return this.sortDirection; },
|
||||
'asset_type': function() { return this.assetType; },
|
||||
'format': 'json'
|
||||
},
|
||||
|
||||
|
||||
@@ -5,12 +5,18 @@ define(["backbone"], function(Backbone) {
|
||||
var Asset = Backbone.Model.extend({
|
||||
defaults: {
|
||||
display_name: "",
|
||||
content_type: "",
|
||||
thumbnail: "",
|
||||
date_added: "",
|
||||
url: "",
|
||||
external_url: "",
|
||||
portable_url: "",
|
||||
locked: false
|
||||
},
|
||||
get_extension: function(){
|
||||
var name_segments = this.get("display_name").split(".").reverse();
|
||||
var asset_type = (name_segments.length > 1) ? name_segments[0].toUpperCase() : "";
|
||||
return asset_type;
|
||||
}
|
||||
});
|
||||
return Asset;
|
||||
|
||||
@@ -35,14 +35,14 @@ define(
|
||||
|
||||
var makeUploadUrl = function(fileName) {
|
||||
return "http://www.example.com/test_url/" + fileName;
|
||||
}
|
||||
};
|
||||
|
||||
var getSentRequests = function() {
|
||||
return _.filter(
|
||||
ajaxRequests,
|
||||
function(request) { return request.readyState > 0; }
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
_.each(
|
||||
[
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
define([ "jquery", "js/common_helpers/ajax_helpers", "js/views/asset", "js/views/assets",
|
||||
define([ "jquery", "js/common_helpers/ajax_helpers", "URI", "js/views/asset", "js/views/assets",
|
||||
"js/models/asset", "js/collections/asset", "js/spec_helpers/view_helpers"],
|
||||
function ($, AjaxHelpers, AssetView, AssetsView, AssetModel, AssetCollection, ViewHelpers) {
|
||||
function ($, AjaxHelpers, URI, AssetView, AssetsView, AssetModel, AssetCollection, ViewHelpers) {
|
||||
|
||||
describe("Assets", function() {
|
||||
var assetsView, mockEmptyAssetsResponse, mockAssetUploadResponse, mockFileUpload,
|
||||
@@ -28,6 +28,7 @@ define([ "jquery", "js/common_helpers/ajax_helpers", "js/views/asset", "js/views
|
||||
collection: collection,
|
||||
el: $('#asset_table_body')
|
||||
});
|
||||
|
||||
assetsView.render();
|
||||
});
|
||||
|
||||
@@ -50,6 +51,68 @@ define([ "jquery", "js/common_helpers/ajax_helpers", "js/views/asset", "js/views
|
||||
totalCount: 0
|
||||
};
|
||||
|
||||
var mockExampleAssetsResponse = {
|
||||
sort: "uploadDate",
|
||||
end: 2,
|
||||
assets: [
|
||||
{
|
||||
"display_name": "test.jpg",
|
||||
"url": "/c4x/A/CS102/asset/test.jpg",
|
||||
"date_added": "Nov 07, 2014 at 17:47 UTC",
|
||||
"id": "/c4x/A/CS102/asset/test.jpg",
|
||||
"portable_url": "/static/test.jpg",
|
||||
"thumbnail": "/c4x/A/CS102/thumbnail/test.jpg",
|
||||
"locked": false,
|
||||
"external_url": "localhost:8000/c4x/A/CS102/asset/test.jpg"
|
||||
},
|
||||
{
|
||||
"display_name": "test.pdf",
|
||||
"url": "/c4x/A/CS102/asset/test.pdf",
|
||||
"date_added": "Oct 20, 2014 at 11:00 UTC",
|
||||
"id": "/c4x/A/CS102/asset/test.pdf",
|
||||
"portable_url": "/static/test.pdf",
|
||||
"thumbnail": null,
|
||||
"locked": false,
|
||||
"external_url": "localhost:8000/c4x/A/CS102/asset/test.pdf"
|
||||
},
|
||||
{
|
||||
"display_name": "test.odt",
|
||||
"url": "/c4x/A/CS102/asset/test.odt",
|
||||
"date_added": "Oct 20, 2014 at 11:00 UTC",
|
||||
"id": "/c4x/A/CS102/asset/test.odt",
|
||||
"portable_url": "/static/test.odt",
|
||||
"thumbnail": null,
|
||||
"locked": false,
|
||||
"external_url": "localhost:8000/c4x/A/CS102/asset/test.odt"
|
||||
}
|
||||
],
|
||||
pageSize: 2,
|
||||
totalCount: 2,
|
||||
start: 0,
|
||||
page: 0
|
||||
};
|
||||
|
||||
var mockExampleFilteredAssetsResponse = {
|
||||
sort: "uploadDate",
|
||||
end: 1,
|
||||
assets: [
|
||||
{
|
||||
"display_name": "test.jpg",
|
||||
"url": "/c4x/A/CS102/asset/test.jpg",
|
||||
"date_added": "Nov 07, 2014 at 17:47 UTC",
|
||||
"id": "/c4x/A/CS102/asset/test.jpg",
|
||||
"portable_url": "/static/test.jpg",
|
||||
"thumbnail": "/c4x/A/CS102/thumbnail/test.jpg",
|
||||
"locked": false,
|
||||
"external_url": "localhost:8000/c4x/A/CS102/asset/test.jpg"
|
||||
}
|
||||
],
|
||||
pageSize: 1,
|
||||
totalCount: 1,
|
||||
start: 0,
|
||||
page: 0
|
||||
};
|
||||
|
||||
mockAssetUploadResponse = {
|
||||
asset: mockAsset,
|
||||
msg: "Upload completed"
|
||||
@@ -59,16 +122,30 @@ define([ "jquery", "js/common_helpers/ajax_helpers", "js/views/asset", "js/views
|
||||
files: [{name: 'largefile', size: 0}]
|
||||
};
|
||||
|
||||
var event = {}
|
||||
var respondWithMockAssets = function(requests) {
|
||||
var requestIndex = requests.length - 1;
|
||||
var request = requests[requestIndex];
|
||||
var url = new URI(request.url);
|
||||
var queryParameters = url.query(true); // Returns an object with each query parameter stored as a value
|
||||
var asset_type = queryParameters.asset_type;
|
||||
var response = asset_type !== '' ? mockExampleFilteredAssetsResponse : mockExampleAssetsResponse;
|
||||
AjaxHelpers.respondWithJson(requests, response, requestIndex);
|
||||
};
|
||||
|
||||
var event = {};
|
||||
event.target = {"value": "dummy.jpg"};
|
||||
|
||||
describe("AssetsView", function () {
|
||||
var setup;
|
||||
setup = function() {
|
||||
var requests;
|
||||
requests = AjaxHelpers.requests(this);
|
||||
setup = function(responseData) {
|
||||
var requests = AjaxHelpers.requests(this);
|
||||
assetsView.setPage(0);
|
||||
AjaxHelpers.respondWithJson(requests, mockEmptyAssetsResponse);
|
||||
if (!responseData){
|
||||
AjaxHelpers.respondWithJson(requests, mockEmptyAssetsResponse);
|
||||
}
|
||||
else{
|
||||
AjaxHelpers.respondWithJson(requests, responseData);
|
||||
}
|
||||
return requests;
|
||||
};
|
||||
|
||||
@@ -170,6 +247,106 @@ define([ "jquery", "js/common_helpers/ajax_helpers", "js/views/asset", "js/views
|
||||
expect(assetsView.largeFileErrorMsg).toBeNull();
|
||||
});
|
||||
|
||||
it('returns the registered info for a filter column', function () {
|
||||
assetsView.registerSortableColumn('test-col', 'Test Column', 'testField', 'asc');
|
||||
assetsView.registerFilterableColumn('js-asset-type-col', 'Type', 'asset_type');
|
||||
var filterInfo = assetsView.filterableColumnInfo('js-asset-type-col');
|
||||
expect(filterInfo.displayName).toBe('Type');
|
||||
expect(filterInfo.fieldName).toBe('asset_type');
|
||||
});
|
||||
|
||||
it('throws an exception for an unregistered filter column', function () {
|
||||
expect(function() {
|
||||
assetsView.filterableColumnInfo('no-such-column');
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
|
||||
it('make sure selectFilter sets collection filter if undefined', function () {
|
||||
expect(assetsView).toBeDefined();
|
||||
assetsView.collection.filterField = '';
|
||||
assetsView.selectFilter('js-asset-type-col');
|
||||
expect(assetsView.collection.filterField).toEqual('asset_type');
|
||||
});
|
||||
|
||||
it('make sure _toggleFilterColumn filters asset list', function () {
|
||||
expect(assetsView).toBeDefined();
|
||||
var requests = AjaxHelpers.requests(this);
|
||||
$.each(assetsView.filterableColumns, function(columnID, columnData){
|
||||
var $typeColumn = $('#' + columnID);
|
||||
assetsView.setPage(0);
|
||||
respondWithMockAssets(requests);
|
||||
var assetsNumber = assetsView.collection.length;
|
||||
assetsView._toggleFilterColumn('Images', 'Images');
|
||||
respondWithMockAssets(requests);
|
||||
var assetsNumberFiltered = assetsView.collection.length;
|
||||
expect(assetsNumberFiltered).toBeLessThan(assetsNumber);
|
||||
expect($typeColumn.find('.title .type-filter')).not.toEqual(assetsView.allLabel);
|
||||
});
|
||||
});
|
||||
|
||||
it('opens and closes select type menu', function () {
|
||||
expect(assetsView).toBeDefined();
|
||||
setup.call(this, mockExampleAssetsResponse);
|
||||
$.each(assetsView.filterableColumns, function(columnID, columnData){
|
||||
var $typeColumn = $('#' + columnID);
|
||||
expect($typeColumn).toBeVisible();
|
||||
var assetsNumber = $('#asset-table-body .type-col').length;
|
||||
assetsView.openFilterColumn($typeColumn);
|
||||
expect($typeColumn.find('.wrapper-nav-sub')).toHaveClass('is-shown');
|
||||
expect($typeColumn.find('.title')).toHaveClass('is-selected');
|
||||
expect($typeColumn.find('.column-filter-link')).toBeVisible();
|
||||
$typeColumn.find('.wrapper-nav-sub').trigger('click');
|
||||
expect($typeColumn.find('.wrapper-nav-sub').hasClass('is-shown')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('check filtering works with sorting by column on', function () {
|
||||
expect(assetsView).toBeDefined();
|
||||
var requests = AjaxHelpers.requests(this);
|
||||
assetsView.registerSortableColumn('name-col', 'Name Column', 'nameField', 'asc');
|
||||
assetsView.registerFilterableColumn('js-asset-type-col', gettext('Type'), 'asset_type');
|
||||
assetsView.setInitialSortColumn('name-col');
|
||||
assetsView.setPage(0);
|
||||
respondWithMockAssets(requests);
|
||||
var sortInfo = assetsView.sortableColumnInfo('name-col');
|
||||
expect(sortInfo.defaultSortDirection).toBe('asc');
|
||||
var $firstFilter = $($('#js-asset-type-col').find('li.nav-item a')[1]);
|
||||
$firstFilter.trigger('click');
|
||||
respondWithMockAssets(requests);
|
||||
var assetsNumberFiltered = assetsView.collection.length;
|
||||
expect(assetsNumberFiltered).toBe(1);
|
||||
|
||||
});
|
||||
|
||||
it('shows type select menu, selects type, and filters results', function () {
|
||||
expect(assetsView).toBeDefined();
|
||||
var requests = AjaxHelpers.requests(this);
|
||||
$.each(assetsView.filterableColumns, function(columnID, columnData) {
|
||||
assetsView.setPage(0);
|
||||
respondWithMockAssets(requests);
|
||||
var $typeColumn = $('#' + columnID);
|
||||
expect($typeColumn).toBeVisible();
|
||||
var assetsNumber = assetsView.collection.length;
|
||||
$typeColumn.trigger('click');
|
||||
expect($typeColumn.find('.wrapper-nav-sub')).toHaveClass('is-shown');
|
||||
expect($typeColumn.find('.title')).toHaveClass('is-selected');
|
||||
var $allFilter = $($typeColumn.find('li.nav-item a')[0]);
|
||||
var $firstFilter = $($typeColumn.find('li.nav-item a')[1]);
|
||||
var $otherFilter = $($typeColumn.find('li.nav-item a[data-assetfilter="OTHER"]')[0]);
|
||||
var select_filter_and_check = function($filterEl, result) {
|
||||
$filterEl.trigger('click');
|
||||
respondWithMockAssets(requests);
|
||||
var assetsNumberFiltered = assetsView.collection.length;
|
||||
expect(assetsNumberFiltered).toBe(result);
|
||||
};
|
||||
|
||||
select_filter_and_check($firstFilter, 1);
|
||||
select_filter_and_check($allFilter, assetsNumber);
|
||||
select_filter_and_check($otherFilter, 1);
|
||||
});
|
||||
});
|
||||
|
||||
it('hides the error modal if a large file, then small file is uploaded', function() {
|
||||
expect(assetsView).toBeDefined();
|
||||
mockFileUpload.files[0].size = assetsView.maxFileSize;
|
||||
|
||||
@@ -58,7 +58,9 @@ define([ "jquery", "js/common_helpers/ajax_helpers", "URI",
|
||||
initialize : function() {
|
||||
this.registerSortableColumn('name-col', 'Name', 'name', 'asc');
|
||||
this.registerSortableColumn('date-col', 'Date', 'date', 'desc');
|
||||
this.registerFilterableColumn('js-asset-type-col', gettext('Type'), 'asset_type');
|
||||
this.setInitialSortColumn('date-col');
|
||||
this.setInitialFilterColumn('js-asset-type-col');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -183,6 +185,7 @@ define([ "jquery", "js/common_helpers/ajax_helpers", "URI",
|
||||
|
||||
it('returns the registered info for a column', function () {
|
||||
pagingView.registerSortableColumn('test-col', 'Test Column', 'testField', 'asc');
|
||||
pagingView.registerFilterableColumn('js-asset-type-col', gettext('Type'), 'asset_type');
|
||||
var sortInfo = pagingView.sortableColumnInfo('test-col');
|
||||
expect(sortInfo.displayName).toBe('Test Column');
|
||||
expect(sortInfo.fieldName).toBe('testField');
|
||||
|
||||
@@ -20,6 +20,7 @@ var AssetView = BaseView.extend({
|
||||
url: this.model.get('url'),
|
||||
external_url: this.model.get('external_url'),
|
||||
portable_url: this.model.get('portable_url'),
|
||||
asset_type: this.model.get_extension(),
|
||||
uniqueId: uniqueId
|
||||
}));
|
||||
this.updateLockState();
|
||||
|
||||
@@ -10,9 +10,16 @@ define(["jquery", "underscore", "gettext", "js/models/asset", "js/views/paging",
|
||||
|
||||
events : {
|
||||
"click .column-sort-link": "onToggleColumn",
|
||||
"click .upload-button": "showUploadModal"
|
||||
"click .upload-button": "showUploadModal",
|
||||
"click .filterable-column .nav-item": "onFilterColumn",
|
||||
"click .filterable-column .column-filter-link": "toggleFilterColumn"
|
||||
},
|
||||
|
||||
typeData: ['Images', 'Documents'],
|
||||
|
||||
allLabel: 'ALL',
|
||||
|
||||
|
||||
initialize : function(options) {
|
||||
options = options || {};
|
||||
|
||||
@@ -22,7 +29,9 @@ define(["jquery", "underscore", "gettext", "js/models/asset", "js/views/paging",
|
||||
this.listenTo(collection, 'destroy', this.handleDestroy);
|
||||
this.registerSortableColumn('js-asset-name-col', gettext('Name'), 'display_name', 'asc');
|
||||
this.registerSortableColumn('js-asset-date-col', gettext('Date Added'), 'date_added', 'desc');
|
||||
this.registerFilterableColumn('js-asset-type-col', gettext('Type'), 'asset_type');
|
||||
this.setInitialSortColumn('js-asset-date-col');
|
||||
this.setInitialFilterColumn('js-asset-type-col');
|
||||
ViewUtils.showLoadingIndicator();
|
||||
this.setPage(0);
|
||||
// set default file size for uploads via template var,
|
||||
@@ -56,7 +65,7 @@ define(["jquery", "underscore", "gettext", "js/models/asset", "js/views/paging",
|
||||
ViewUtils.hideLoadingIndicator();
|
||||
|
||||
// Create the table
|
||||
this.$el.html(this.template());
|
||||
this.$el.html(this.template({typeData: this.typeData}));
|
||||
tableBody = this.$('#asset-table-body');
|
||||
this.tableBody = tableBody;
|
||||
this.pagingHeader = new PagingHeader({view: this, el: $('#asset-paging-header')});
|
||||
@@ -74,7 +83,7 @@ define(["jquery", "underscore", "gettext", "js/models/asset", "js/views/paging",
|
||||
renderPageItems: function() {
|
||||
var self = this,
|
||||
assets = this.collection,
|
||||
hasAssets = assets.length > 0,
|
||||
hasAssets = this.collection.assetType !== '' || assets.length > 0,
|
||||
tableBody = this.getTableBody();
|
||||
tableBody.empty();
|
||||
if (hasAssets) {
|
||||
@@ -106,6 +115,7 @@ define(["jquery", "underscore", "gettext", "js/models/asset", "js/views/paging",
|
||||
// Switch the sort column back to the default (most recent date added) and show the first page
|
||||
// so that the new asset is shown at the top of the page.
|
||||
this.setInitialSortColumn('js-asset-date-col');
|
||||
this.setInitialFilterColumn('js-asset-type-col');
|
||||
this.setPage(0);
|
||||
|
||||
analytics.track('Uploaded a File', {
|
||||
@@ -119,6 +129,11 @@ define(["jquery", "underscore", "gettext", "js/models/asset", "js/views/paging",
|
||||
this.toggleSortOrder(columnName);
|
||||
},
|
||||
|
||||
onFilterColumn: function(event) {
|
||||
this.openFilterColumn($(event.currentTarget));
|
||||
event.stopPropagation();
|
||||
},
|
||||
|
||||
hideModal: function (event) {
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
@@ -222,6 +237,65 @@ define(["jquery", "underscore", "gettext", "js/models/asset", "js/views/paging",
|
||||
$('.upload-modal .progress-fill').html(percentVal);
|
||||
},
|
||||
|
||||
openFilterColumn: function($this) {
|
||||
this.toggleFilterColumnState($this);
|
||||
},
|
||||
|
||||
toggleFilterColumnState: function(menu, selected) {
|
||||
var $subnav = menu.find('.wrapper-nav-sub');
|
||||
var $title = menu.find('.title');
|
||||
var titleText = $title.find('.type-filter');
|
||||
var assettype = selected ? selected.data('assetfilter'): false;
|
||||
if(assettype) {
|
||||
if(assettype === this.allLabel) {
|
||||
titleText.text(titleText.data('alllabel'));
|
||||
}
|
||||
else {
|
||||
titleText.text(assettype);
|
||||
}
|
||||
}
|
||||
if ($subnav.hasClass('is-shown')) {
|
||||
$subnav.removeClass('is-shown');
|
||||
$title.removeClass('is-selected');
|
||||
} else {
|
||||
$title.addClass('is-selected');
|
||||
$subnav.addClass('is-shown');
|
||||
}
|
||||
},
|
||||
|
||||
toggleFilterColumn: function(event) {
|
||||
event.preventDefault();
|
||||
var $filterColumn = $(event.currentTarget);
|
||||
this._toggleFilterColumn($filterColumn.data('assetfilter'), $filterColumn.text());
|
||||
},
|
||||
|
||||
_toggleFilterColumn: function(assettype, assettypeLabel) {
|
||||
var collection = this.collection;
|
||||
var filterColumn = this.$el.find('.filterable-column');
|
||||
var resetFilter = filterColumn.find('.reset-filter');
|
||||
var title = filterColumn.find('.title');
|
||||
if(assettype === this.allLabel) {
|
||||
collection.assetType = '';
|
||||
resetFilter.hide();
|
||||
title.removeClass('column-selected-link');
|
||||
}
|
||||
else {
|
||||
collection.assetType = assettype;
|
||||
resetFilter.show();
|
||||
title.addClass('column-selected-link');
|
||||
}
|
||||
|
||||
this.filterableColumns['js-asset-type-col'].displayName = assettypeLabel;
|
||||
this.selectFilter('js-asset-type-col');
|
||||
this.closeFilterPopup(this.$el.find(
|
||||
'.column-filter-link[data-assetfilter="' + assettype + '"]'));
|
||||
},
|
||||
|
||||
closeFilterPopup: function(element){
|
||||
var $menu = element.parents('.nav-dd > .nav-item');
|
||||
this.toggleFilterColumnState($menu, element);
|
||||
},
|
||||
|
||||
displayFinishedUpload: function (resp) {
|
||||
var asset = resp.asset;
|
||||
|
||||
|
||||
@@ -6,6 +6,10 @@ define(["underscore", "js/views/baseview", "js/views/feedback_alert", "gettext",
|
||||
|
||||
sortableColumns: {},
|
||||
|
||||
filterableColumns: {},
|
||||
|
||||
filterColumn: '',
|
||||
|
||||
initialize: function() {
|
||||
BaseView.prototype.initialize.call(this);
|
||||
var collection = this.collection;
|
||||
@@ -25,6 +29,51 @@ define(["underscore", "js/views/baseview", "js/views/feedback_alert", "gettext",
|
||||
// Do nothing by default
|
||||
},
|
||||
|
||||
nextPage: function() {
|
||||
var collection = this.collection,
|
||||
currentPage = collection.currentPage,
|
||||
lastPage = collection.totalPages - 1;
|
||||
if (currentPage < lastPage) {
|
||||
this.setPage(currentPage + 1);
|
||||
}
|
||||
},
|
||||
|
||||
previousPage: function() {
|
||||
var collection = this.collection,
|
||||
currentPage = collection.currentPage;
|
||||
if (currentPage > 0) {
|
||||
this.setPage(currentPage - 1);
|
||||
}
|
||||
},
|
||||
|
||||
registerFilterableColumn: function(columnName, displayName, fieldName) {
|
||||
this.filterableColumns[columnName] = {
|
||||
displayName: displayName,
|
||||
fieldName: fieldName
|
||||
};
|
||||
},
|
||||
|
||||
filterableColumnInfo: function(filterColumn) {
|
||||
var filterInfo = this.filterableColumns[filterColumn];
|
||||
if (!filterInfo) {
|
||||
throw "Unregistered filter column '" + filterInfo + '"';
|
||||
}
|
||||
return filterInfo;
|
||||
},
|
||||
|
||||
filterDisplayName: function() {
|
||||
var filterColumn = this.filterColumn,
|
||||
filterInfo = this.filterableColumnInfo(filterColumn);
|
||||
return filterInfo.displayName;
|
||||
},
|
||||
|
||||
setInitialFilterColumn: function(filterColumn) {
|
||||
var collection = this.collection,
|
||||
filtertInfo = this.filterableColumns[filterColumn];
|
||||
collection.filterField = filtertInfo.fieldName;
|
||||
this.filterColumn = filterColumn;
|
||||
},
|
||||
|
||||
/**
|
||||
* Registers information about a column that can be sorted.
|
||||
* @param columnName The element name of the column.
|
||||
@@ -75,6 +124,18 @@ define(["underscore", "js/views/baseview", "js/views/feedback_alert", "gettext",
|
||||
}
|
||||
this.sortColumn = sortColumn;
|
||||
this.setPage(0);
|
||||
},
|
||||
|
||||
selectFilter: function(filterColumn) {
|
||||
var collection = this.collection,
|
||||
filterInfo = this.filterableColumnInfo(filterColumn),
|
||||
filterField = filterInfo.fieldName,
|
||||
defaultFilterKey = false;
|
||||
if (collection.filterField !== filterField) {
|
||||
collection.filterField = filterField;
|
||||
}
|
||||
this.filterColumn = filterColumn;
|
||||
this.setPage(0);
|
||||
}
|
||||
});
|
||||
return PagingView;
|
||||
|
||||
@@ -31,17 +31,40 @@ define(["underscore", "gettext", "js/views/baseview"], function(_, gettext, Base
|
||||
},
|
||||
|
||||
messageHtml: function() {
|
||||
var message;
|
||||
if (this.view.collection.sortDirection === 'asc') {
|
||||
// Translators: sample result: "Showing 0-9 out of 25 total, sorted by Date Added ascending"
|
||||
message = gettext('Showing %(current_item_range)s out of %(total_items_count)s, sorted by %(sort_name)s ascending');
|
||||
} else {
|
||||
// Translators: sample result: "Showing 0-9 out of 25 total, sorted by Date Added descending"
|
||||
message = gettext('Showing %(current_item_range)s out of %(total_items_count)s, sorted by %(sort_name)s descending');
|
||||
var message = '';
|
||||
var asset_type = false;
|
||||
if (this.view.collection.assetType) {
|
||||
if (this.view.collection.sortDirection === 'asc') {
|
||||
// Translators: sample result:
|
||||
// "Showing 0-9 out of 25 total, filtered by Images, sorted by Date Added ascending"
|
||||
message = gettext('Showing %(current_item_range)s out of %(total_items_count)s, ' +
|
||||
'filtered by %(asset_type)s, sorted by %(sort_name)s ascending');
|
||||
} else {
|
||||
// Translators: sample result:
|
||||
// "Showing 0-9 out of 25 total, filtered by Images, sorted by Date Added descending"
|
||||
message = gettext('Showing %(current_item_range)s out of %(total_items_count)s, ' +
|
||||
'filtered by %(asset_type)s, sorted by %(sort_name)s descending');
|
||||
}
|
||||
asset_type = this.filterNameLabel();
|
||||
}
|
||||
else {
|
||||
if (this.view.collection.sortDirection === 'asc') {
|
||||
// Translators: sample result:
|
||||
// "Showing 0-9 out of 25 total, sorted by Date Added ascending"
|
||||
message = gettext('Showing %(current_item_range)s out of %(total_items_count)s, ' +
|
||||
'sorted by %(sort_name)s ascending');
|
||||
} else {
|
||||
// Translators: sample result:
|
||||
// "Showing 0-9 out of 25 total, sorted by Date Added descending"
|
||||
message = gettext('Showing %(current_item_range)s out of %(total_items_count)s, ' +
|
||||
'sorted by %(sort_name)s descending');
|
||||
}
|
||||
}
|
||||
|
||||
return '<p>' + interpolate(message, {
|
||||
current_item_range: this.currentItemRangeLabel(),
|
||||
total_items_count: this.totalItemsCountLabel(),
|
||||
asset_type: asset_type,
|
||||
sort_name: this.sortNameLabel()
|
||||
}, true) + "</p>";
|
||||
},
|
||||
@@ -75,6 +98,12 @@ define(["underscore", "gettext", "js/views/baseview"], function(_, gettext, Base
|
||||
}, true);
|
||||
},
|
||||
|
||||
filterNameLabel: function() {
|
||||
return interpolate('<span class="filter-column">%(filter_name)s</span>', {
|
||||
filter_name: this.view.filterDisplayName()
|
||||
}, true);
|
||||
},
|
||||
|
||||
nextPage: function() {
|
||||
this.view.nextPage();
|
||||
},
|
||||
|
||||
@@ -43,6 +43,351 @@
|
||||
}
|
||||
}
|
||||
|
||||
.assets-library {
|
||||
@include clearfix;
|
||||
|
||||
.meta-wrap {
|
||||
margin-bottom: $baseline;
|
||||
}
|
||||
.meta {
|
||||
@extend %t-copy-sub2;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
width: flex-grid(9, 12);
|
||||
color: $gray-l1;
|
||||
|
||||
.count-current-shown,
|
||||
.count-total,
|
||||
.filter-column,
|
||||
.sort-order {
|
||||
@extend %t-strong;
|
||||
}
|
||||
}
|
||||
|
||||
.pagination {
|
||||
@include clearfix;
|
||||
display: inline-block;
|
||||
width: flex-grid(3, 12);
|
||||
|
||||
&.pagination-compact {
|
||||
@include text-align(right);
|
||||
}
|
||||
|
||||
&.pagination-full {
|
||||
display: block;
|
||||
width: flex-grid(4, 12);
|
||||
margin: $baseline auto;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
@include transition(all $tmg-f2 ease-in-out 0s);
|
||||
display: block;
|
||||
padding: ($baseline/4) ($baseline*0.75);
|
||||
|
||||
&.previous {
|
||||
margin-right: ($baseline/2);
|
||||
}
|
||||
|
||||
&.next {
|
||||
margin-left: ($baseline/2);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: $blue;
|
||||
border-radius: 3px;
|
||||
color: $white;
|
||||
}
|
||||
|
||||
&.is-disabled {
|
||||
background-color: transparent;
|
||||
color: $gray-l2;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-label {
|
||||
@extend .sr;
|
||||
}
|
||||
|
||||
.pagination-form,
|
||||
.current-page,
|
||||
.page-divider,
|
||||
.total-pages {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.current-page,
|
||||
.page-number-input,
|
||||
.total-pages {
|
||||
@extend %t-copy-base;
|
||||
@extend %t-strong;
|
||||
width: ($baseline*2.5);
|
||||
margin: 0 ($baseline*0.75);
|
||||
padding: ($baseline/4);
|
||||
text-align: center;
|
||||
color: $gray;
|
||||
}
|
||||
|
||||
.current-page {
|
||||
@extend %ui-depth1;
|
||||
position: absolute;
|
||||
@include left(-($baseline/4));
|
||||
}
|
||||
|
||||
.page-divider {
|
||||
@extend %t-title4;
|
||||
@extend %t-regular;
|
||||
vertical-align: middle;
|
||||
color: $gray-l2;
|
||||
}
|
||||
|
||||
|
||||
.pagination-form {
|
||||
@extend %ui-depth2;
|
||||
position: relative;
|
||||
|
||||
.page-number-label,
|
||||
.submit-pagination-form {
|
||||
@extend .sr;
|
||||
}
|
||||
|
||||
.page-number-input {
|
||||
@include transition(all $tmg-f2 ease-in-out 0s);
|
||||
border: 1px solid transparent;
|
||||
border-bottom: 1px dotted $gray-l2;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
background: none;
|
||||
|
||||
&:hover {
|
||||
background-color: $white;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
// borrowing the base input focus styles to match overall app
|
||||
@include linear-gradient($paleYellow, tint($paleYellow, 90%));
|
||||
opacity: 1.0;
|
||||
box-shadow: 0 0 3px $shadow-d1 inset;
|
||||
background-color: $white;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
word-wrap: break-word;
|
||||
|
||||
th {
|
||||
@extend %t-copy-sub2;
|
||||
background-color: $gray-l5;
|
||||
padding: 0 ($baseline/2) ($baseline*0.75) ($baseline/2);
|
||||
vertical-align: middle;
|
||||
text-align: left;
|
||||
color: $gray;
|
||||
|
||||
.column-sort-link, .column-selected-link {
|
||||
cursor: pointer;
|
||||
color: $blue;
|
||||
}
|
||||
|
||||
.current-sort {
|
||||
@extend %t-strong;
|
||||
border-bottom: 1px solid $gray-l3;
|
||||
}
|
||||
|
||||
// CASE: embed column
|
||||
&.embed-col {
|
||||
padding-left: ($baseline*0.75);
|
||||
padding-right: ($baseline*0.75);
|
||||
}
|
||||
|
||||
&.nav-dd{
|
||||
// basic layout - nav items
|
||||
margin: 0 -($baseline/2);
|
||||
color: $blue;
|
||||
cursor: pointer;
|
||||
.wrapper-nav-sub {
|
||||
top: 35px;
|
||||
@extend %ui-depth2;
|
||||
|
||||
> ol > .nav-item {
|
||||
@extend %t-action3;
|
||||
@extend %t-strong;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
.nav-sub {
|
||||
@include text-align(left);
|
||||
|
||||
// ui triangle/nub
|
||||
&:after {
|
||||
left: $baseline;
|
||||
margin-left: -10px;
|
||||
}
|
||||
|
||||
&:before {
|
||||
left: $baseline;
|
||||
margin-left: -11px;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
&.reset-filter{
|
||||
display:none;
|
||||
}
|
||||
|
||||
a {
|
||||
color: $gray-d1;
|
||||
|
||||
&:hover {
|
||||
color: $blue-s1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
padding: ($baseline/2);
|
||||
vertical-align: middle;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
tbody {
|
||||
box-shadow: 0 2px 2px $shadow-l1;
|
||||
border: 1px solid $gray-l4;
|
||||
background: $white;
|
||||
|
||||
tr {
|
||||
@include transition(all $tmg-f2 ease-in-out 0s);
|
||||
border-top: 1px solid $gray-l4;
|
||||
|
||||
&:first-child {
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
&:nth-child(odd) {
|
||||
background-color: $gray-l6;
|
||||
}
|
||||
|
||||
a {
|
||||
color: $gray-d1;
|
||||
|
||||
&:hover {
|
||||
color: $blue;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-locked {
|
||||
background-image: url('../images/bg-micro-stripes.png');
|
||||
background-position: 0 0;
|
||||
background-repeat: repeat;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: $blue-l5;
|
||||
|
||||
.date-col,
|
||||
.embed-col,
|
||||
.embed-col .embeddable-xml-input {
|
||||
color: $gray;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.thumb-col {
|
||||
padding: ($baseline/2) $baseline;
|
||||
@extend %t-copy-sub2;
|
||||
color: $gray-l2;
|
||||
|
||||
.thumb {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.name-col {
|
||||
|
||||
.title {
|
||||
@extend %t-copy-sub1;
|
||||
display: inline-block;
|
||||
max-width: 200px;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.type-col {
|
||||
@extend %t-copy-sub2;
|
||||
color: $gray-l2;
|
||||
}
|
||||
|
||||
.date-col {
|
||||
@include transition(all $tmg-f2 ease-in-out 0s);
|
||||
@extend %t-copy-sub2;
|
||||
color: $gray-l2;
|
||||
}
|
||||
|
||||
.embed-col {
|
||||
@include transition(all $tmg-f2 ease-in-out 0s);
|
||||
@extend %t-copy-sub2;
|
||||
padding-left: ($baseline*0.75);
|
||||
color: $gray-l2;
|
||||
|
||||
.label {
|
||||
display: inline-block;
|
||||
width: ($baseline*2);
|
||||
}
|
||||
|
||||
.embeddable-xml-input {
|
||||
@include transition(all $tmg-f2 ease-in-out 0s);
|
||||
@extend %t-copy-sub2;
|
||||
box-shadow: none;
|
||||
border: 1px solid transparent;
|
||||
background: none;
|
||||
padding: ($baseline/5);
|
||||
color: $gray-l2;
|
||||
|
||||
&:focus {
|
||||
background-color: $white;
|
||||
box-shadow: 0 1px 5px $shadow-l1 inset;
|
||||
border: 1px solid $gray-l3;
|
||||
color: $black;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.actions-col {
|
||||
padding: ($baseline/2);
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// UI: assets - calls-to-action
|
||||
.actions-list {
|
||||
@extend %actions-list;
|
||||
|
||||
@@ -70,10 +70,11 @@
|
||||
<div class="bit">
|
||||
<h3 class="title-3">${_("Using File URLs")}</h3>
|
||||
|
||||
<p>${_("Use the {em_start}Embed URL{em_end} value to link to the file or image from a component, a course update, or a course handout.").format(em_start='<strong>', em_end="</strong>")}</p>
|
||||
<p>${_("Use the {em_start}{studio_name} URL{em_end} value to link to the file or image from a component, a course update, or a course handout.").format(studio_name=settings.STUDIO_SHORT_NAME, em_start="<strong>", em_end="</strong>")}</p>
|
||||
|
||||
<p>${_("Use the {em_start}External URL{em_end} value to reference the file or image only from outside of your course.").format(em_start='<strong>', em_end="</strong>")}</p>
|
||||
<p>${_("Click in the Embed URL or External URL column to select the value, then copy it.")}</p>
|
||||
<p>${_("Use the {em_start}Web URL{em_end} value to reference the file or image only from outside of your course. {em_start}Note:{em_end} If you lock a file, the Web URL no longer works for external access to a file.").format(em_start='<strong>', em_end="</strong>")}</p>
|
||||
|
||||
<p>${_("To copy a URL, double click the value in the URL column, then copy the selected text.")}</p>
|
||||
</div>
|
||||
<div class="bit external-help">
|
||||
<a href="${get_online_help_info(online_help_token())['doc_url']}" target="_blank" class="button external-help-button">${_("Learn more about managing files")}</a>
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<div class="assets-library">
|
||||
<div id="asset-paging-header"></div>
|
||||
|
||||
|
||||
<table class="assets-table">
|
||||
<caption class="sr"><%= gettext("List of uploaded files and assets in this course") %></caption>
|
||||
<colgroup>
|
||||
<col class="thumb-cols" />
|
||||
<col class="name-cols" />
|
||||
<col class="type-cols" />
|
||||
<col class="date-cols" />
|
||||
<col class="embed-cols" />
|
||||
<col class="actions-cols" />
|
||||
@@ -13,10 +14,46 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="thumb-col"><%= gettext("Preview") %></th>
|
||||
<th class="name-col sortable-column"><span class="column-sort-link" id="js-asset-name-col"><%= gettext("Name") %></span></th>
|
||||
<th class="date-col sortable-column"><span class="column-sort-link" id="js-asset-date-col"><%= gettext("Date Added") %></span></th>
|
||||
<th class="embed-col"><%= gettext("Embed URL") %></th>
|
||||
<th class="embed-col"><%= gettext("External URL") %></th>
|
||||
<th class="name-col sortable-column">
|
||||
<span class="column-sort-link" id="js-asset-name-col" role="button" tabindex="0">
|
||||
<%= gettext("Name") %>
|
||||
<span class="sr"><%= gettext("- Sortable") %></span>
|
||||
</span>
|
||||
</th>
|
||||
<th class="type-col filterable-column nav-dd">
|
||||
<div id="js-asset-type-col" class="nav-item" role="button" tabindex="0">
|
||||
<span class="title">
|
||||
<span class="type-filter" data-alllabel='<%= gettext("Type") %>'><%= gettext("Type") %></span>
|
||||
<span class="label-prefix sr">Filter</span>
|
||||
<span class="filter-link"></span>
|
||||
<i class="fa fa-caret-down ui-toggle-dd" aria-hidden="true"></i>
|
||||
</span>
|
||||
<div class="wrapper-nav-sub">
|
||||
<div class="nav-sub">
|
||||
<ul>
|
||||
<li class="nav-item reset-filter">
|
||||
<a class="column-filter-link" href="" data-assetfilter="ALL">Show All</a>
|
||||
</li>
|
||||
<% _.each(typeData, function(type, key){ %>
|
||||
<li class="nav-item">
|
||||
<a class="column-filter-link" href="" data-assetfilter="<%= type %>"><%= type %></a>
|
||||
</li>
|
||||
<% }) %>
|
||||
<li class="nav-item">
|
||||
<a class="column-filter-link" href="" data-assetfilter="OTHER">Other</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</th>
|
||||
<th class="date-col sortable-column">
|
||||
<span class="column-sort-link" id="js-asset-date-col">
|
||||
<%= gettext("Date Added") %>
|
||||
<span class="sr"><%= gettext("- Sortable") %></span>
|
||||
</span>
|
||||
</th>
|
||||
<th class="embed-col"><%= gettext("URL") %></th>
|
||||
<th class="actions-col"><span class="sr"><%= gettext("Actions") %></span></th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -27,5 +64,5 @@
|
||||
</div>
|
||||
|
||||
<div class="no-asset-content">
|
||||
<p><%= gettext("You haven't added any assets to this course yet.") %> <a href="#" class="button new-button upload-button"><i class="icon fa fa-plus"></i><%= gettext("Upload your first asset") %></a></p>
|
||||
<p><%= gettext("You haven't added any assets to this course yet.") %> <a href="#" class="button new-button upload-button"><i class="icon fa fa-plus" aria-hidden="true"></i><%= gettext("Upload your first asset") %></a></p>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<div class="upload-modal modal" style="display: none;">
|
||||
<a href="#" class="close-button"><i class="icon fa fa-times-circle"></i> <span class="sr"><%= gettext('close') %></span></a>
|
||||
<a href="#" class="close-button"><i class="icon fa fa-times-circle" aria-hidden="true"></i> <span class="sr"><%= gettext('close') %></span></a>
|
||||
<div class="modal-body">
|
||||
<h1 class="title"><%= gettext("Upload New File") %></h1>
|
||||
<p class="file-name">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<td class="thumb-col">
|
||||
<div class="thumb">
|
||||
<% if (thumbnail !== '') { %>
|
||||
<img src="<%= thumbnail %>">
|
||||
<img src="<%= thumbnail %>" alt="<%= gettext('No description available') %>">
|
||||
<% } %>
|
||||
</div>
|
||||
</td>
|
||||
@@ -10,24 +10,38 @@
|
||||
|
||||
<div class="embeddable-xml"></div>
|
||||
</td>
|
||||
<td class="type-col">
|
||||
<%= asset_type %>
|
||||
</td>
|
||||
<td class="date-col">
|
||||
<%= date_added %>
|
||||
</td>
|
||||
<td class="embed-col">
|
||||
<input type="text" class="embeddable-xml-input" value="<%= portable_url %>" readonly dir="ltr">
|
||||
</td>
|
||||
<td class="embed-col">
|
||||
<input type="text" class="embeddable-xml-input" value="<%= external_url %>" readonly>
|
||||
<ul>
|
||||
<li class="embed-url">
|
||||
<label>
|
||||
<span class="label"><%= gettext('Studio:') %></span>
|
||||
<input type="text" class="embeddable-xml-input" value="<%= portable_url %>" readonly>
|
||||
</label>
|
||||
</li>
|
||||
<li class="external-url">
|
||||
<label>
|
||||
<span class="label"><%= gettext('Web:') %></span>
|
||||
<input type="text" class="embeddable-xml-input" value="<%= external_url %>" readonly>
|
||||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</td>
|
||||
<td class="actions-col">
|
||||
<ul class="actions-list">
|
||||
<li class="action-item action-delete">
|
||||
<a href="#" data-tooltip="<%= gettext('Delete this asset') %>" class="remove-asset-button action-button"><i class="icon fa fa-times-circle"></i> <span class="sr"><%= gettext('Delete this asset') %></span></a>
|
||||
<a href="#" data-tooltip="<%= gettext('Delete this asset') %>" class="remove-asset-button action-button"><i class="icon fa fa-times-circle" aria-hidden="true"></i> <span class="sr"><%= gettext('Delete this asset') %></span></a>
|
||||
</li>
|
||||
<li class="action-item action-lock">
|
||||
<label for="<%= uniqueId %>"><span class="sr"><%= gettext('Lock this asset') %></span></label>
|
||||
<input type="checkbox" id="<%= uniqueId %>" class="lock-checkbox" data-tooltip="<%= gettext('Lock/unlock file') %>" />
|
||||
<div class="action-button"><i class="icon fa fa-lock"></i><i class="icon fa fa-unlock-alt"></i></div>
|
||||
<div class="action-button"><i class="icon fa fa-lock"></i><i class="icon fa fa-unlock-alt" aria-hidden="true"></i></div>
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
|
||||
@@ -104,8 +104,7 @@
|
||||
</li>
|
||||
|
||||
<li class="nav-item nav-course-tools">
|
||||
<h3 class="title"><span class="label">${_("Tools")}</span> <i class="icon fa fa-caret-down ui-toggle-dd"></i></h3>
|
||||
|
||||
<h3 class="title"><span class="label">${_("Tools")}</span> <i class="icon fa fa-caret-down ui-toggle-dd" aria-hidden="true"></i></h3>
|
||||
<div class="wrapper wrapper-nav-sub">
|
||||
<div class="nav-sub">
|
||||
<ul>
|
||||
|
||||
@@ -220,7 +220,7 @@ class ContentStore(object):
|
||||
def find(self, filename):
|
||||
raise NotImplementedError
|
||||
|
||||
def get_all_content_for_course(self, course_key, start=0, maxresults=-1, sort=None):
|
||||
def get_all_content_for_course(self, course_key, start=0, maxresults=-1, sort=None, filter_params=None):
|
||||
'''
|
||||
Returns a list of static assets for a course, followed by the total number of assets.
|
||||
By default all assets are returned, but start and maxresults can be provided to limit the query.
|
||||
|
||||
@@ -172,9 +172,9 @@ class MongoContentStore(ContentStore):
|
||||
def get_all_content_thumbnails_for_course(self, course_key):
|
||||
return self._get_all_content_for_course(course_key, get_thumbnails=True)[0]
|
||||
|
||||
def get_all_content_for_course(self, course_key, start=0, maxresults=-1, sort=None):
|
||||
def get_all_content_for_course(self, course_key, start=0, maxresults=-1, sort=None, filter_params=None):
|
||||
return self._get_all_content_for_course(
|
||||
course_key, start=start, maxresults=maxresults, get_thumbnails=False, sort=sort
|
||||
course_key, start=start, maxresults=maxresults, get_thumbnails=False, sort=sort, filter_params=filter_params
|
||||
)
|
||||
|
||||
def remove_redundant_content_for_courses(self):
|
||||
@@ -197,7 +197,13 @@ class MongoContentStore(ContentStore):
|
||||
self.fs_files.remove(query)
|
||||
return assets_to_delete
|
||||
|
||||
def _get_all_content_for_course(self, course_key, get_thumbnails=False, start=0, maxresults=-1, sort=None):
|
||||
def _get_all_content_for_course(self,
|
||||
course_key,
|
||||
get_thumbnails=False,
|
||||
start=0,
|
||||
maxresults=-1,
|
||||
sort=None,
|
||||
filter_params=None):
|
||||
'''
|
||||
Returns a list of all static assets for a course. The return format is a list of asset data dictionary elements.
|
||||
|
||||
@@ -208,15 +214,17 @@ class MongoContentStore(ContentStore):
|
||||
contentType: The mimetype string of the asset
|
||||
md5: An md5 hash of the asset content
|
||||
'''
|
||||
query = query_for_course(course_key, "asset" if not get_thumbnails else "thumbnail")
|
||||
find_args = {"sort": sort}
|
||||
if maxresults > 0:
|
||||
items = self.fs_files.find(
|
||||
query_for_course(course_key, "asset" if not get_thumbnails else "thumbnail"),
|
||||
skip=start, limit=maxresults, sort=sort
|
||||
)
|
||||
else:
|
||||
items = self.fs_files.find(
|
||||
query_for_course(course_key, "asset" if not get_thumbnails else "thumbnail"), sort=sort
|
||||
)
|
||||
find_args.update({
|
||||
"skip": start,
|
||||
"limit": maxresults,
|
||||
})
|
||||
if filter_params:
|
||||
query.update(filter_params)
|
||||
|
||||
items = self.fs_files.find(query, **find_args)
|
||||
count = items.count()
|
||||
assets = list(items)
|
||||
|
||||
|
||||
@@ -333,6 +333,10 @@ class TestMongoModuleStore(TestMongoModuleStoreBase):
|
||||
location = Location('edX', 'toy', '2012_Fall', 'course', '2012_Fall')
|
||||
course_content, __ = self.content_store.get_all_content_for_course(location.course_key)
|
||||
assert_true(len(course_content) > 0)
|
||||
filter_params = _build_requested_filter('Images')
|
||||
filtered_course_content, __ = self.content_store.get_all_content_for_course(
|
||||
location.course_key, filter_params=filter_params)
|
||||
assert_true(len(filtered_course_content) < len(course_content))
|
||||
# a bit overkill, could just do for content[0]
|
||||
for content in course_content:
|
||||
assert not content.get('locked', False)
|
||||
@@ -778,3 +782,35 @@ class TestMongoKeyValueStore(object):
|
||||
for scope in (Scope.preferences, Scope.user_info, Scope.user_state, Scope.parent):
|
||||
with assert_raises(InvalidScopeError):
|
||||
self.kvs.delete(KeyValueStore.Key(scope, None, None, 'foo'))
|
||||
|
||||
|
||||
def _build_requested_filter(requested_filter):
|
||||
"""
|
||||
Returns requested filter_params string.
|
||||
"""
|
||||
|
||||
# Files and Uploads type filter values
|
||||
all_filters = {
|
||||
"Images": ['image/png', 'image/jpeg', 'image/jpg', 'image/gif', 'image/tiff', 'image/tif', 'image/x-icon'],
|
||||
"Documents": [
|
||||
'application/pdf',
|
||||
'text/plain',
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
|
||||
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
||||
'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
|
||||
'application/vnd.openxmlformats-officedocument.presentationml.template',
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
|
||||
'application/msword',
|
||||
'application/vnd.ms-excel',
|
||||
'application/vnd.ms-powerpoint',
|
||||
],
|
||||
}
|
||||
requested_file_types = all_filters.get(requested_filter, None)
|
||||
where = ["JSON.stringify(this.contentType).toUpperCase() == JSON.stringify('{}').toUpperCase()".format(
|
||||
req_filter) for req_filter in requested_file_types]
|
||||
filter_params = {
|
||||
"$where": ' || '.join(where),
|
||||
}
|
||||
return filter_params
|
||||
|
||||
@@ -2,15 +2,84 @@
|
||||
The Files and Uploads page for a course in Studio
|
||||
"""
|
||||
|
||||
import urllib
|
||||
import os
|
||||
from opaque_keys.edx.locator import CourseLocator
|
||||
from . import BASE_URL
|
||||
from .course_page import CoursePage
|
||||
from bok_choy.javascript import wait_for_js, requirejs
|
||||
|
||||
|
||||
@requirejs('js/views/assets')
|
||||
class AssetIndexPage(CoursePage):
|
||||
|
||||
"""
|
||||
The Files and Uploads page for a course in Studio
|
||||
"""
|
||||
|
||||
url_path = "assets"
|
||||
type_filter_element = '#js-asset-type-col'
|
||||
|
||||
@property
|
||||
def url(self):
|
||||
"""
|
||||
Construct a URL to the page within the course.
|
||||
"""
|
||||
# TODO - is there a better way to make this agnostic to the underlying default module store?
|
||||
default_store = os.environ.get('DEFAULT_STORE', 'draft')
|
||||
course_key = CourseLocator(
|
||||
self.course_info['course_org'],
|
||||
self.course_info['course_num'],
|
||||
self.course_info['course_run'],
|
||||
deprecated=(default_store == 'draft')
|
||||
)
|
||||
url = "/".join([BASE_URL, self.url_path, urllib.quote_plus(unicode(course_key))])
|
||||
return url if url[-1] is '/' else url + '/'
|
||||
|
||||
@wait_for_js
|
||||
def is_browser_on_page(self):
|
||||
return self.q(css='body.view-uploads').present
|
||||
|
||||
@wait_for_js
|
||||
def type_filter_on_page(self):
|
||||
"""
|
||||
Checks that type filter is in table header.
|
||||
"""
|
||||
return self.q(css=self.type_filter_element).present
|
||||
|
||||
@wait_for_js
|
||||
def type_filter_header_label_visible(self):
|
||||
"""
|
||||
Checks type filter label is added and visible in the pagination header.
|
||||
"""
|
||||
return self.q(css='span.filter-column').visible
|
||||
|
||||
@wait_for_js
|
||||
def click_type_filter(self):
|
||||
"""
|
||||
Clicks type filter menu.
|
||||
"""
|
||||
self.q(css=".filterable-column .nav-item").click()
|
||||
|
||||
@wait_for_js
|
||||
def select_type_filter(self, filter_number):
|
||||
"""
|
||||
Selects Type filter from dropdown which filters the results.
|
||||
Returns False if no filter.
|
||||
"""
|
||||
self.wait_for_ajax()
|
||||
if self.q(css=".filterable-column .nav-item").is_present():
|
||||
if not self.q(css=self.type_filter_element + " .wrapper-nav-sub").visible:
|
||||
self.q(css=".filterable-column > .nav-item").first.click()
|
||||
self.wait_for_element_visibility(
|
||||
self.type_filter_element + " .wrapper-nav-sub", "Type Filter promise satisfied.")
|
||||
self.q(css=self.type_filter_element + " .column-filter-link").nth(filter_number).click()
|
||||
self.wait_for_ajax()
|
||||
return True
|
||||
return False
|
||||
|
||||
def return_results_set(self):
|
||||
"""
|
||||
Returns the asset set from the page
|
||||
"""
|
||||
return self.q(css="#asset-table-body tr").results
|
||||
|
||||
57
common/test/acceptance/tests/studio/test_studio_asset.py
Normal file
57
common/test/acceptance/tests/studio/test_studio_asset.py
Normal file
@@ -0,0 +1,57 @@
|
||||
"""
|
||||
Acceptance tests for Studio related to the asset index page.
|
||||
"""
|
||||
|
||||
from ...pages.studio.asset_index import AssetIndexPage
|
||||
|
||||
from acceptance.tests.studio.base_studio_test import StudioCourseTest
|
||||
from acceptance.fixtures.base import StudioApiLoginError
|
||||
|
||||
|
||||
class AssetIndexTest(StudioCourseTest):
|
||||
|
||||
"""
|
||||
Tests for the Asset index page.
|
||||
"""
|
||||
|
||||
def setUp(self, is_staff=False):
|
||||
super(AssetIndexTest, self).setUp()
|
||||
self.asset_page = AssetIndexPage(
|
||||
self.browser,
|
||||
self.course_info['org'],
|
||||
self.course_info['number'],
|
||||
self.course_info['run']
|
||||
)
|
||||
|
||||
def populate_course_fixture(self, course_fixture):
|
||||
"""
|
||||
Populate the children of the test course fixture.
|
||||
"""
|
||||
self.course_fixture.add_asset(['image.jpg', 'textbook.pdf'])
|
||||
|
||||
def test_page_existence(self):
|
||||
"""
|
||||
Make sure that the page is accessible.
|
||||
"""
|
||||
self.asset_page.visit()
|
||||
|
||||
def test_type_filter_exists(self):
|
||||
"""
|
||||
Make sure type filter is on the page.
|
||||
"""
|
||||
self.asset_page.visit()
|
||||
assert self.asset_page.type_filter_on_page() is True
|
||||
|
||||
def test_filter_results(self):
|
||||
"""
|
||||
Make sure type filter actually filters the results.
|
||||
"""
|
||||
self.asset_page.visit()
|
||||
all_results = len(self.asset_page.return_results_set())
|
||||
if self.asset_page.select_type_filter(1):
|
||||
filtered_results = len(self.asset_page.return_results_set())
|
||||
assert self.asset_page.type_filter_header_label_visible()
|
||||
assert all_results > filtered_results
|
||||
else:
|
||||
msg = "Could not open select Type filter"
|
||||
raise StudioApiLoginError(msg)
|
||||
@@ -51,6 +51,7 @@ class LibraryEditPageTest(StudioLibraryTest):
|
||||
Then one XBlock is displayed
|
||||
And displayed XBlock are second one
|
||||
"""
|
||||
self.browser.save_screenshot('library_page')
|
||||
self.assertEqual(len(self.lib_page.xblocks), 0)
|
||||
|
||||
# Create a new block:
|
||||
|
||||
Reference in New Issue
Block a user