Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,5 @@ Carson Gee <cgee@mit.edu>
Gang Chen <goncha@gmail.com>
Bertrand Marron <bertrand.marron@ionis-group.com>
Yihua Lou <supermouselyh@hotmail.com>
Andy Armstrong <andya@edx.org>

2 changes: 2 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ These are notable changes in edx-platform. This is a rolling list of changes,
in roughly chronological order, most recent first. Add your entries at or near
the top. Include a label indicating the component affected.

Studio: Added pagination to the Files & Uploads page.

Blades: Video player improvements:
- Disable edX controls on iPhone/iPod (native controls are used).
- Disable unsupported controls (volume, playback rate) on iPad/Android.
Expand Down
98 changes: 12 additions & 86 deletions cms/djangoapps/contentstore/tests/test_assets.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def test_static_url_generation(self):

class AssetsToyCourseTestCase(CourseTestCase):
"""
Tests the assets returned from assets_handler (full page content) for the toy test course.
Tests the assets returned from assets_handler for the toy test course.
"""
def test_toy_assets(self):
module_store = modulestore('direct')
Expand All @@ -56,10 +56,17 @@ def test_toy_assets(self):
location = loc_mapper().translate_location(course.location.course_id, course.location, False, True)
url = location.url_reverse('assets/', '')

resp = self.client.get(url, HTTP_ACCEPT='text/html')
# Test a small portion of the asset data passed to the client.
self.assertContains(resp, "new AssetCollection([{")
self.assertContains(resp, "/c4x/edX/toy/asset/handouts_sample_handout.txt")
self.assert_correct_asset_response(url, 0, 3, 3)
self.assert_correct_asset_response(url + "?page_size=2", 0, 2, 3)
self.assert_correct_asset_response(url + "?page_size=2&page=1", 2, 1, 3)

def assert_correct_asset_response(self, url, expected_start, expected_length, expected_total):
resp = self.client.get(url, HTTP_ACCEPT='application/json')
json_response = json.loads(resp.content)
assets = json_response['assets']
self.assertEquals(json_response['start'], expected_start)
self.assertEquals(len(assets), expected_length)
self.assertEquals(json_response['totalCount'], expected_total)


class UploadTestCase(CourseTestCase):
Expand All @@ -82,10 +89,6 @@ def test_no_file(self):
resp = self.client.post(self.url, {"name": "file.txt"}, "application/json")
self.assertEquals(resp.status_code, 400)

def test_get(self):
with self.assertRaises(NotImplementedError):
self.client.get(self.url)


class AssetToJsonTestCase(TestCase):
"""
Expand Down Expand Up @@ -163,80 +166,3 @@ def post_asset_update(lock):
resp_asset = post_asset_update(False)
self.assertFalse(resp_asset['locked'])
verify_asset_locked_state(False)


class TestAssetIndex(CourseTestCase):
"""
Test getting asset lists via http (Note, the assets don't actually exist)
"""
def setUp(self):
"""
Create fake asset entries for the other tests to use
"""
super(TestAssetIndex, self).setUp()
self.entry_filter = self.create_asset_entries(contentstore(), 100)
location = loc_mapper().translate_location(self.course.location.course_id, self.course.location, False, True)
self.url = location.url_reverse('assets/', '')

def tearDown(self):
"""
Get rid of the entries
"""
contentstore().fs_files.remove(self.entry_filter)

def create_asset_entries(self, cstore, number):
"""
Create the fake entries
"""
course_filter = Location(
XASSET_LOCATION_TAG, category='asset', course=self.course.location.course, org=self.course.location.org
)
# purge existing entries (a bit brutal but hopefully tests are independent enuf to not trip on this)
cstore.fs_files.remove(location_to_query(course_filter))
base_entry = {
'displayname': 'foo.jpg',
'chunkSize': 262144,
'length': 0,
'uploadDate': datetime(2012, 1, 2, 0, 0),
'contentType': 'image/jpeg',
}
for i in range(number):
base_entry['displayname'] = '{:03x}.jpeg'.format(i)
base_entry['uploadDate'] += timedelta(hours=i)
base_entry['_id'] = course_filter.replace(name=base_entry['displayname']).dict()
cstore.fs_files.insert(base_entry)

return course_filter.dict()

ASSET_LIST_RE = re.compile(r'AssetCollection\((.*)\);$', re.MULTILINE)

def check_page_content(self, resp_content, entry_count, last_date=None):
"""
:param entry_count:
:param last_date:
"""
match = self.ASSET_LIST_RE.search(resp_content)
asset_list = json.loads(match.group(1))
self.assertEqual(len(asset_list), entry_count)
for row in asset_list:
datetext = row['date_added']
parsed_date = datetime.strptime(datetext, "%b %d, %Y at %H:%M UTC")
if last_date is None:
last_date = parsed_date
else:
self.assertGreaterEqual(last_date, parsed_date)
return last_date

def test_query_assets(self):
"""
The actual test
"""
# get all
resp = self.client.get(self.url, HTTP_ACCEPT='text/html')
self.check_page_content(resp.content, 100)
# get first page of 10
resp = self.client.get(self.url + "?max=10", HTTP_ACCEPT='text/html')
last_date = self.check_page_content(resp.content, 10)
# get next of 20
resp = self.client.get(self.url + "?start=10&max=20", HTTP_ACCEPT='text/html')
self.check_page_content(resp.content, 20, last_date)
12 changes: 7 additions & 5 deletions cms/djangoapps/contentstore/tests/test_contentstore.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ def _lock_an_asset(self, content_store, course_location):
Lock an arbitrary asset in the course
:param course_location:
"""
course_assets = content_store.get_all_content_for_course(course_location)
course_assets,__ = content_store.get_all_content_for_course(course_location)
self.assertGreater(len(course_assets), 0, "No assets to lock")
content_store.set_attr(course_assets[0]['_id'], 'locked', True)
return course_assets[0]['_id']
Expand Down Expand Up @@ -585,7 +585,7 @@ def test_asset_import(self):
self.assertIsNotNone(course)

# make sure we have some assets in our contentstore
all_assets = content_store.get_all_content_for_course(course_location)
all_assets,__ = content_store.get_all_content_for_course(course_location)
self.assertGreater(len(all_assets), 0)

# make sure we have some thumbnails in our contentstore
Expand Down Expand Up @@ -698,7 +698,7 @@ def test_empty_trashcan(self):

# make sure there's something in the trashcan
course_location = CourseDescriptor.id_to_location('edX/toy/6.002_Spring_2012')
all_assets = trash_store.get_all_content_for_course(course_location)
all_assets,__ = trash_store.get_all_content_for_course(course_location)
self.assertGreater(len(all_assets), 0)

# make sure we have some thumbnails in our trashcan
Expand All @@ -713,8 +713,9 @@ def test_empty_trashcan(self):
empty_asset_trashcan([course_location])

# make sure trashcan is empty
all_assets = trash_store.get_all_content_for_course(course_location)
all_assets,count = trash_store.get_all_content_for_course(course_location)
self.assertEqual(len(all_assets), 0)
self.assertEqual(count, 0)

all_thumbnails = trash_store.get_all_content_thumbnails_for_course(course_location)
self.assertEqual(len(all_thumbnails), 0)
Expand Down Expand Up @@ -923,8 +924,9 @@ def test_delete_course(self):
self.assertEqual(len(items), 0)

# assert that all content in the asset library is also deleted
assets = content_store.get_all_content_for_course(location)
assets,count = content_store.get_all_content_for_course(location)
self.assertEqual(len(assets), 0)
self.assertEqual(count, 0)

def verify_content_existence(self, store, root_dir, location, dirname, category_name, filename_suffix=''):
filesystem = OSFS(root_dir / 'test_export')
Expand Down
6 changes: 4 additions & 2 deletions cms/djangoapps/contentstore/tests/test_import_nostatic.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,10 @@ def test_static_import(self):
_, content_store, course, course_location = self.load_test_import_course()

# make sure we have ONE asset in our contentstore ("should_be_imported.html")
all_assets = content_store.get_all_content_for_course(course_location)
all_assets,count = content_store.get_all_content_for_course(course_location)
print "len(all_assets)=%d" % len(all_assets)
self.assertEqual(len(all_assets), 1)
self.assertEqual(count, 1)

content = None
try:
Expand Down Expand Up @@ -114,9 +115,10 @@ def test_asset_import_nostatic(self):
module_store.get_item(course_location)

# make sure we have NO assets in our contentstore
all_assets = content_store.get_all_content_for_course(course_location)
all_assets,count = content_store.get_all_content_for_course(course_location)
print "len(all_assets)=%d" % len(all_assets)
self.assertEqual(len(all_assets), 0)
self.assertEqual(count, 0)

def test_no_static_link_rewrites_on_import(self):
module_store = modulestore('direct')
Expand Down
61 changes: 38 additions & 23 deletions cms/djangoapps/contentstore/views/assets.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,10 @@ def assets_handler(request, tag=None, package_id=None, branch=None, version_guid
deleting assets, and changing the "locked" state of an asset.

GET
html: return html page of all course assets (note though that a range of assets can be requested using start
and max query parameters)
json: not currently supported
html: return html page which will show all course assets. Note that only the asset container
is returned and that the actual assets are filled in with a client-side request.
json: returns a page of assets. A page parameter specifies the desired page, and the
optional page_size parameter indicates the number of items per page (defaults to 50).
POST
json: create (or update?) an asset. The only updating that can be done is changing the lock state.
PUT
Expand All @@ -55,9 +56,10 @@ def assets_handler(request, tag=None, package_id=None, branch=None, version_guid
if not has_access(request.user, location):
raise PermissionDenied()

if 'application/json' in request.META.get('HTTP_ACCEPT', 'application/json'):
response_format = request.REQUEST.get('format', 'html')
if response_format == 'json' or 'application/json' in request.META.get('HTTP_ACCEPT', 'application/json'):
if request.method == 'GET':
raise NotImplementedError('coming soon')
return _assets_json(request, location)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update the documentation, which says that JSON is not supported.

Also, documentation around what is returned for HTML needs to be updated.

else:
return _update_asset(request, location, asset_id)
elif request.method == 'GET': # assume html
Expand All @@ -73,22 +75,32 @@ def _asset_index(request, location):
Supports start (0-based index into the list of assets) and max query parameters.
"""
old_location = loc_mapper().translate_locator_to_location(location)

course_module = modulestore().get_item(old_location)
maxresults = request.REQUEST.get('max', None)
start = request.REQUEST.get('start', None)

return render_to_response('asset_index.html', {
'context_course': course_module,
'asset_callback_url': location.url_reverse('assets/', '')
})


def _assets_json(request, location):
"""
Display an editable asset library.

Supports start (0-based index into the list of assets) and max query parameters.
"""
requested_page = int(request.REQUEST.get('page', 0))
requested_page_size = int(request.REQUEST.get('page_size', 50))
current_page = max(requested_page, 0)
start = current_page * requested_page_size

old_location = loc_mapper().translate_locator_to_location(location)

course_reference = StaticContent.compute_location(old_location.org, old_location.course, old_location.name)
if maxresults is not None:
maxresults = int(maxresults)
start = int(start) if start else 0
assets = contentstore().get_all_content_for_course(
course_reference, start=start, maxresults=maxresults,
sort=[('uploadDate', DESCENDING)]
)
else:
assets = contentstore().get_all_content_for_course(
course_reference, sort=[('uploadDate', DESCENDING)]
)
assets, total_count = contentstore().get_all_content_for_course(
course_reference, start=start, maxresults=requested_page_size, sort=[('uploadDate', DESCENDING)]
)
end = start + len(assets)

asset_json = []
for asset in assets:
Expand All @@ -101,10 +113,13 @@ def _asset_index(request, location):
asset_locked = asset.get('locked', False)
asset_json.append(_get_asset_json(asset['displayname'], asset['uploadDate'], asset_location, thumbnail_location, asset_locked))

return render_to_response('asset_index.html', {
'context_course': course_module,
'asset_list': json.dumps(asset_json),
'asset_callback_url': location.url_reverse('assets/', '')
return JsonResponse({
'start': start,
'end': end,
'page': current_page,
'pageSize': requested_page_size,
'totalCount': total_count,
'assets': asset_json
})


Expand Down
16 changes: 14 additions & 2 deletions cms/static/coffee/spec/main.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ requirejs.config({
"underscore.string": "xmodule_js/common_static/js/vendor/underscore.string.min",
"backbone": "xmodule_js/common_static/js/vendor/backbone-min",
"backbone.associations": "xmodule_js/common_static/js/vendor/backbone-associations-min",
"backbone.paginator": "xmodule_js/common_static/js/vendor/backbone.paginator.min",
"tinymce": "xmodule_js/common_static/js/vendor/tiny_mce/tiny_mce",
"jquery.tinymce": "xmodule_js/common_static/js/vendor/tiny_mce/jquery.tinymce",
"xmodule": "xmodule_js/src/xmodule",
Expand All @@ -38,6 +39,7 @@ requirejs.config({
"jasmine.async": "xmodule_js/common_static/js/vendor/jasmine.async",
"draggabilly": "xmodule_js/common_static/js/vendor/draggabilly.pkgd",
"domReady": "xmodule_js/common_static/js/vendor/domReady",
"URI": "xmodule_js/common_static/js/vendor/URI.min",

"mathjax": "//edx-static.s3.amazonaws.com/mathjax-MathJax-727332c/MathJax.js?config=TeX-MML-AM_HTMLorMML-full&delayStartupUntil=configured",
"youtube": "//www.youtube.com/player_api?noext",
Expand Down Expand Up @@ -115,6 +117,10 @@ requirejs.config({
deps: ["backbone"],
exports: "Backbone.Associations"
},
"backbone.paginator": {
deps: ["backbone"],
exports: "Backbone.Paginator"
},
"youtube": {
exports: "YT"
},
Expand All @@ -139,6 +145,9 @@ requirejs.config({
]
MathJax.Hub.Configured()
},
"URI": {
exports: "URI"
},
"xmodule": {
exports: "XModule"
},
Expand Down Expand Up @@ -197,10 +206,13 @@ define([
"js/spec/transcripts/videolist_spec", "js/spec/transcripts/message_manager_spec",
"js/spec/transcripts/file_uploader_spec",

"js/spec/utils/module_spec",
"js/spec/models/explicit_url_spec"
"js/spec/views/baseview_spec",

"js/spec/utils/handle_iframe_binding_spec",
"js/spec/utils/module_spec",

"js/spec/views/baseview_spec",
"js/spec/views/paging_spec",

# these tests are run separate in the cms-squire suite, due to process
# isolation issues with Squire.js
Expand Down
10 changes: 9 additions & 1 deletion cms/static/coffee/spec/main_squire.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ requirejs.config({
"underscore.string": "xmodule_js/common_static/js/vendor/underscore.string.min",
"backbone": "xmodule_js/common_static/js/vendor/backbone-min",
"backbone.associations": "xmodule_js/common_static/js/vendor/backbone-associations-min",
"backbone.paginator": "xmodule_js/common_static/js/vendor/backbone.paginator.min",
"tinymce": "xmodule_js/common_static/js/vendor/tiny_mce/tiny_mce",
"jquery.tinymce": "xmodule_js/common_static/js/vendor/tiny_mce/jquery.tinymce",
"xmodule": "xmodule_js/src/xmodule",
Expand All @@ -34,6 +35,7 @@ requirejs.config({
"jasmine.async": "xmodule_js/common_static/js/vendor/jasmine.async",
"draggabilly": "xmodule_js/common_static/js/vendor/draggabilly.pkgd",
"domReady": "xmodule_js/common_static/js/vendor/domReady",
"URI": "xmodule_js/common_static/js/vendor/URI.min",

"mathjax": "//edx-static.s3.amazonaws.com/mathjax-MathJax-727332c/MathJax.js?config=TeX-MML-AM_HTMLorMML-full&delayStartupUntil=configured",
"youtube": "//www.youtube.com/player_api?noext",
Expand Down Expand Up @@ -106,6 +108,10 @@ requirejs.config({
deps: ["backbone"],
exports: "Backbone.Associations"
},
"backbone.paginator": {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might as well add "URI" export like you did in main.coffee to keep the configuration consistent.

deps: ["backbone"],
exports: "Backbone.Paginator"
},
"youtube": {
exports: "YT"
},
Expand All @@ -130,6 +136,9 @@ requirejs.config({
]
MathJax.Hub.Configured();
},
"URI": {
exports: "URI"
},
"xmodule": {
exports: "XModule"
},
Expand Down Expand Up @@ -166,4 +175,3 @@ jasmine.getFixtures().fixturesPath += 'coffee/fixtures'
define([
"coffee/spec/views/assets_spec"
])

Loading