diff --git a/cms/djangoapps/contentstore/utils.py b/cms/djangoapps/contentstore/utils.py index bc33c7d1f6..fe1a64f5cf 100644 --- a/cms/djangoapps/contentstore/utils.py +++ b/cms/djangoapps/contentstore/utils.py @@ -74,3 +74,8 @@ def compute_unit_state(unit): return UnitState.private else: return UnitState.public + + +def get_date_display(date): + print date, type(date) + return date.strftime("%d %B, %Y at %I:%M %p") diff --git a/cms/djangoapps/contentstore/views.py b/cms/djangoapps/contentstore/views.py index fa9bd30797..26ed3c53d2 100644 --- a/cms/djangoapps/contentstore/views.py +++ b/cms/djangoapps/contentstore/views.py @@ -1,3 +1,4 @@ +import traceback from util.json_request import expect_json import exceptions import json @@ -44,7 +45,7 @@ from cache_toolbox.core import set_cached_content, get_cached_content, del_cache from auth.authz import is_user_in_course_group_role, get_users_in_course_group_by_role from auth.authz import get_user_by_email, add_user_to_course_group, remove_user_from_course_group from auth.authz import INSTRUCTOR_ROLE_NAME, STAFF_ROLE_NAME -from .utils import get_course_location_for_item, get_lms_link_for_item, compute_unit_state +from .utils import get_course_location_for_item, get_lms_link_for_item, compute_unit_state, get_date_display from xmodule.templates import all_templates @@ -618,7 +619,7 @@ def upload_asset(request, org, course, coursename): if not has_access(request.user, location): return HttpResponseForbidden() - # Does the course actually exist?!? + # Does the course actually exist?!? Get anything from it to prove its existance try: item = modulestore().get_item(location) @@ -635,23 +636,11 @@ def upload_asset(request, org, course, coursename): mime_type = request.FILES['file'].content_type filedata = request.FILES['file'].read() - file_location = StaticContent.compute_location(org, course, name) + thumbnail_file_location = None - content = StaticContent(file_location, name, mime_type, filedata) - - # first commit to the DB - contentstore().save(content) - - # then remove the cache so we're not serving up stale content - # NOTE: we're not re-populating the cache here as the DB owns the last-modified timestamp - # which is used when serving up static content. This integrity is needed for - # browser-side caching support. We *could* re-fetch the saved content so that we have the - # timestamp populated, but we might as well wait for the first real request to come in - # to re-populate the cache. - del_cached_content(content.location) - - # if we're uploading an image, then let's generate a thumbnail so that we can - # serve it up when needed without having to rescale on the fly + # if the upload asset is an image, we can generate a thumbnail from it + # let's do so now, so that we have the thumbnail location which we need + # so that the asset can point to it if mime_type.split('/')[0] == 'image': try: # not sure if this is necessary, but let's rewind the stream just in case @@ -673,24 +662,45 @@ def upload_asset(request, org, course, coursename): thumbnail_file.seek(0) # use a naming convention to associate originals with the thumbnail - thumbnail_name = content.generate_thumbnail_name() + thumbnail_name = StaticContent.generate_thumbnail_name(name) # then just store this thumbnail as any other piece of content thumbnail_file_location = StaticContent.compute_location(org, course, - thumbnail_name) + thumbnail_name, is_thumbnail=True) thumbnail_content = StaticContent(thumbnail_file_location, thumbnail_name, 'image/jpeg', thumbnail_file) contentstore().save(thumbnail_content) - + # remove any cached content at this location, as thumbnails are treated just like any # other bit of static content del_cached_content(thumbnail_content.location) + + # not sure if this is necessary, but let's rewind the stream just in case + request.FILES['file'].seek(0) except: # catch, log, and continue as thumbnails are not a hard requirement logging.error('Failed to generate thumbnail for {0}. Continuing...'.format(name)) + thumbnail_file_location = None - return HttpResponse('Upload completed') + file_location = StaticContent.compute_location(org, course, name) + + # create a StaticContent entity and point to the thumbnail + content = StaticContent(file_location, name, mime_type, filedata, thumbnail_location = thumbnail_file_location) + + # first commit to the DB + contentstore().save(content) + + # then remove the cache so we're not serving up stale content + # NOTE: we're not re-populating the cache here as the DB owns the last-modified timestamp + # which is used when serving up static content. This integrity is needed for + # browser-side caching support. We *could* re-fetch the saved content so that we have the + # timestamp populated, but we might as well wait for the first real request to come in + # to re-populate the cache. + del_cached_content(content.location) + response = HttpResponse('Upload completed') + response['asset_url'] = StaticContent.get_url_path_from_location(file_location) + return response ''' This view will return all CMS users who are editors for the specified course @@ -772,23 +782,69 @@ def remove_user(request, location): return create_json_response() -@login_required -@ensure_csrf_cookie -def asset_index(request, location): - return render_to_response('asset_index.html',{}) # points to the temporary course landing page with log in and sign up def landing(request, org, course, coursename): return render_to_response('temp-course-landing.html', {}) + def static_pages(request, org, course, coursename): return render_to_response('static-pages.html', {}) + def edit_static(request, org, course, coursename): return render_to_response('edit-static-page.html', {}) + def not_found(request): return render_to_response('error.html', {'error': '404'}) + def server_error(request): - return render_to_response('error.html', {'error': '500'}) \ No newline at end of file + return render_to_response('error.html', {'error': '500'}) + + +@login_required +@ensure_csrf_cookie +def asset_index(request, org, course, name): + """ + Display an editable asset library + + org, course, name: Attributes of the Location for the item to edit + """ + location = ['i4x', org, course, 'course', name] + + # check that logged in user has permissions to this item + if not has_access(request.user, location): + raise PermissionDenied() + + upload_asset_callback_url = reverse('upload_asset', kwargs = { + 'org' : org, + 'course' : course, + 'coursename' : name + }) + + course_reference = StaticContent.compute_location(org, course, name) + assets = contentstore().get_all_content_for_course(course_reference) + thumbnails = contentstore().get_all_content_thumbnails_for_course(course_reference) + asset_display = [] + for asset in assets: + id = asset['_id'] + display_info = {} + display_info['displayname'] = asset['displayname'] + display_info['uploadDate'] = get_date_display(asset['uploadDate']) + + asset_location = StaticContent.compute_location(id['org'], id['course'], id['name']) + display_info['url'] = StaticContent.get_url_path_from_location(asset_location) + + # note, due to the schema change we may not have a 'thumbnail_location' in the result set + thumbnail_location = Location(asset.get('thumbnail_location', None)) + + display_info['thumb_url'] = StaticContent.get_url_path_from_location(thumbnail_location) + + asset_display.append(display_info) + + return render_to_response('asset_index.html', { + 'assets': asset_display, + 'upload_asset_callback_url': upload_asset_callback_url + }) diff --git a/cms/static/js/base.js b/cms/static/js/base.js index 42afe992d5..c73eaead44 100644 --- a/cms/static/js/base.js +++ b/cms/static/js/base.js @@ -23,6 +23,10 @@ $(document).ready(function() { $modalCover.bind('click', hideHistoryModal); $('.assets .upload-button').bind('click', showUploadModal); $('.upload-modal .close-button').bind('click', hideModal); + + $('a.show-xml').toggle(showEmbeddableXML, hideEmbeddableXML); + + $('a.copy-button').toggle(showEmbeddableXML, hideEmbeddableXML); $('.unit .item-actions .delete-button').bind('click', deleteUnit); $('.new-unit-item').bind('click', createNewUnit); $('.save-subsection').bind('click', saveSubsection); @@ -72,6 +76,17 @@ function removePolicyMetadata(e) { _parent_el.remove(); else _parent_el.appendTo("#policy-to-delete"); + +function showEmbeddableXML(e) { + $ceiling = $(this).parents('tr'); + if ($ceiling.length === 0) $ceiling = $(this).parents('.upload-modal'); + $ceiling.find('.embeddable-xml').html('<img src="'+$(this).attr('href')+'"/>'); +} +function hideEmbeddableXML(e) { + $ceiling = $(this).parents('tr'); + console.log($ceiling.length) + if ($ceiling.length === 0) $ceiling = $(this).parents('.upload-modal'); + $ceiling.find('.embeddable-xml').html(""); } @@ -233,20 +248,40 @@ function showFileSelectionMenu(e) { function startUpload(e) { $('.upload-modal h1').html('Uploading…'); $('.upload-modal .file-name').html($('.file-input').val()); + $('.upload-modal .file-chooser').ajaxSubmit({ + beforeSend: resetUploadBar, + uploadProgress: showUploadFeedback, + complete: displayFinishedUpload + }); $('.upload-modal .choose-file-button').hide(); $('.upload-modal .progress-bar').removeClass('loaded').show(); - $('.upload-modal .progress-fill').html('').css('width', '0').animate({ - 'width': '100%' - }, 1500); - setTimeout(markAsLoaded, 1500); +} + +function resetUploadBar(){ + var percentVal = '0%'; + $('.upload-modal .progress-fill').width(percentVal) + $('.upload-modal .progress-fill').html(percentVal); +} + +function showUploadFeedback(event, position, total, percentComplete) { + var percentVal = percentComplete + '%'; + $('.upload-modal .progress-fill').width(percentVal); + $('.upload-modal .progress-fill').html(percentVal); +} + +function displayFinishedUpload(xhr) { + if(xhr.status = 200){ + markAsLoaded(); + } + $('.upload-modal .copy-button').attr('href', xhr.getResponseHeader('asset_url')); + $('.upload-modal .progress-fill').html(xhr.responseText); + $('.upload-modal .choose-file-button').html('Load Another File').show(); } function markAsLoaded() { $('.upload-modal .copy-button').css('display', 'inline-block'); $('.upload-modal .progress-bar').addClass('loaded'); - $('.upload-modal .progress-fill').html('loaded successfully'); - $('.upload-modal .choose-file-button').html('Load Another File').show(); -} +} function hideModal(e) { e.preventDefault(); diff --git a/cms/static/sass/_assets.scss b/cms/static/sass/_assets.scss index 82df497c9b..136b03280d 100644 --- a/cms/static/sass/_assets.scss +++ b/cms/static/sass/_assets.scss @@ -86,6 +86,9 @@ } } } + .show-xml { + @include blue-button; + } } .upload-modal { diff --git a/cms/templates/asset_index.html b/cms/templates/asset_index.html index 0069cae9ba..5940767c86 100644 --- a/cms/templates/asset_index.html +++ b/cms/templates/asset_index.html @@ -9,7 +9,8 @@

Asset Library

- Upload New File + Upload New File +
@@ -22,149 +23,26 @@ + % for asset in assets: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + % endfor
-
+
- raygun-1.jpg + ${asset['displayname']} +
- 10/2/2012 + ${asset['uploadDate']} - copy -
-
-
- raygun-2.jpg - - 10/2/2012 - - copy -
-
-
- raygun-2.jpg - - 10/2/2012 - - copy -
-
-
- raygun-1.jpg - - 10/2/2012 - - copy -
-
-
- raygun-2.jpg - - 10/2/2012 - - copy -
-
-
- raygun-2.jpg - - 10/2/2012 - - copy -
-
-
- raygun-1.jpg - - 10/2/2012 - - copy -
-
-
- raygun-2.jpg - - 10/2/2012 - - copy -
-
-
- raygun-2.jpg - - 10/2/2012 - - copy -
-
-
- raygun-1.jpg - - 10/2/2012 - - copy + XML
-
+ diff --git a/cms/templates/base.html b/cms/templates/base.html index 915394f458..f847ad6f7b 100644 --- a/cms/templates/base.html +++ b/cms/templates/base.html @@ -33,6 +33,7 @@ +