Update URLs for assets.
This commit is contained in:
@@ -5,7 +5,7 @@ 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: Change course overview page, checklists, and course staff management
|
||||
Studio: Change course overview page, checklists, assets, and course staff management
|
||||
page URLs to a RESTful interface. Also removed "\listing", which duplicated
|
||||
"\index".
|
||||
|
||||
|
||||
@@ -13,26 +13,24 @@ import json
|
||||
import re
|
||||
from unittest import TestCase, skip
|
||||
from .utils import CourseTestCase
|
||||
from django.core.urlresolvers import reverse
|
||||
from contentstore.views import assets
|
||||
from xmodule.contentstore.content import StaticContent, XASSET_LOCATION_TAG
|
||||
from xmodule.modulestore import Location
|
||||
from xmodule.contentstore.django import contentstore
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.modulestore.xml_importer import import_from_xml
|
||||
from xmodule.modulestore.django import loc_mapper
|
||||
from xmodule.modulestore.mongo.base import location_to_query
|
||||
|
||||
|
||||
class AssetsTestCase(CourseTestCase):
|
||||
def setUp(self):
|
||||
super(AssetsTestCase, self).setUp()
|
||||
self.url = reverse("asset_index", kwargs={
|
||||
'org': self.course.location.org,
|
||||
'course': self.course.location.course,
|
||||
'name': self.course.location.name,
|
||||
})
|
||||
location = loc_mapper().translate_location(self.course.location.course_id, self.course.location, False, True)
|
||||
self.url = location.url_reverse('assets/', '')
|
||||
|
||||
def test_basic(self):
|
||||
resp = self.client.get(self.url)
|
||||
resp = self.client.get(self.url, HTTP_ACCEPT='text/html')
|
||||
self.assertEquals(resp.status_code, 200)
|
||||
|
||||
def test_static_url_generation(self):
|
||||
@@ -43,14 +41,22 @@ class AssetsTestCase(CourseTestCase):
|
||||
|
||||
class AssetsToyCourseTestCase(CourseTestCase):
|
||||
"""
|
||||
Tests the assets returned from asset_index for the toy test course.
|
||||
Tests the assets returned from assets_handler (full page content) for the toy test course.
|
||||
"""
|
||||
def test_toy_assets(self):
|
||||
module_store = modulestore('direct')
|
||||
import_from_xml(module_store, 'common/test/data/', ['toy'], static_content_store=contentstore(), verbose=True)
|
||||
url = reverse("asset_index", kwargs={'org': 'edX', 'course': 'toy', 'name': '2012_Fall'})
|
||||
_, course_items = import_from_xml(
|
||||
module_store,
|
||||
'common/test/data/',
|
||||
['toy'],
|
||||
static_content_store=contentstore(),
|
||||
verbose=True
|
||||
)
|
||||
course = course_items[0]
|
||||
location = loc_mapper().translate_location(course.location.course_id, course.location, False, True)
|
||||
url = location.url_reverse('assets/', '')
|
||||
|
||||
resp = self.client.get(url)
|
||||
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")
|
||||
@@ -62,11 +68,8 @@ class UploadTestCase(CourseTestCase):
|
||||
"""
|
||||
def setUp(self):
|
||||
super(UploadTestCase, self).setUp()
|
||||
self.url = reverse("upload_asset", kwargs={
|
||||
'org': self.course.location.org,
|
||||
'course': self.course.location.course,
|
||||
'coursename': self.course.location.name,
|
||||
})
|
||||
location = loc_mapper().translate_location(self.course.location.course_id, self.course.location, False, True)
|
||||
self.url = location.url_reverse('assets/', '')
|
||||
|
||||
@skip("CorruptGridFile error on continuous integration server")
|
||||
def test_happy_path(self):
|
||||
@@ -76,12 +79,12 @@ class UploadTestCase(CourseTestCase):
|
||||
self.assertEquals(resp.status_code, 200)
|
||||
|
||||
def test_no_file(self):
|
||||
resp = self.client.post(self.url, {"name": "file.txt"})
|
||||
resp = self.client.post(self.url, {"name": "file.txt"}, "application/json")
|
||||
self.assertEquals(resp.status_code, 400)
|
||||
|
||||
def test_get(self):
|
||||
resp = self.client.get(self.url)
|
||||
self.assertEquals(resp.status_code, 405)
|
||||
with self.assertRaises(NotImplementedError):
|
||||
self.client.get(self.url)
|
||||
|
||||
|
||||
class AssetToJsonTestCase(TestCase):
|
||||
@@ -127,16 +130,28 @@ class LockAssetTestCase(CourseTestCase):
|
||||
def post_asset_update(lock):
|
||||
""" Helper method for posting asset update. """
|
||||
upload_date = datetime(2013, 6, 1, 10, 30, tzinfo=UTC)
|
||||
location = Location(['c4x', 'edX', 'toy', 'asset', 'sample_static.txt'])
|
||||
url = reverse('update_asset', kwargs={'org': 'edX', 'course': 'toy', 'name': '2012_Fall'})
|
||||
asset_location = Location(['c4x', 'edX', 'toy', 'asset', 'sample_static.txt'])
|
||||
location = loc_mapper().translate_location(course.location.course_id, course.location, False, True)
|
||||
url = location.url_reverse('assets/', '')
|
||||
|
||||
resp = self.client.post(url, json.dumps(assets._get_asset_json("sample_static.txt", upload_date, location, None, lock)), "application/json")
|
||||
resp = self.client.post(
|
||||
url,
|
||||
json.dumps(assets._get_asset_json("sample_static.txt", upload_date, asset_location, None, lock)),
|
||||
"application/json"
|
||||
)
|
||||
self.assertEqual(resp.status_code, 201)
|
||||
return json.loads(resp.content)
|
||||
|
||||
# Load the toy course.
|
||||
module_store = modulestore('direct')
|
||||
import_from_xml(module_store, 'common/test/data/', ['toy'], static_content_store=contentstore(), verbose=True)
|
||||
_, course_items = import_from_xml(
|
||||
module_store,
|
||||
'common/test/data/',
|
||||
['toy'],
|
||||
static_content_store=contentstore(),
|
||||
verbose=True
|
||||
)
|
||||
course = course_items[0]
|
||||
verify_asset_locked_state(False)
|
||||
|
||||
# Lock the asset
|
||||
@@ -160,6 +175,8 @@ class TestAssetIndex(CourseTestCase):
|
||||
"""
|
||||
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):
|
||||
"""
|
||||
@@ -175,7 +192,7 @@ class TestAssetIndex(CourseTestCase):
|
||||
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(course_filter.dict())
|
||||
cstore.fs_files.remove(location_to_query(course_filter))
|
||||
base_entry = {
|
||||
'displayname': 'foo.jpg',
|
||||
'chunkSize': 262144,
|
||||
@@ -215,19 +232,11 @@ class TestAssetIndex(CourseTestCase):
|
||||
The actual test
|
||||
"""
|
||||
# get all
|
||||
asset_url = reverse(
|
||||
'asset_index',
|
||||
kwargs={
|
||||
'org': self.course.location.org,
|
||||
'course': self.course.location.course,
|
||||
'name': self.course.location.name
|
||||
}
|
||||
)
|
||||
resp = self.client.get(asset_url)
|
||||
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(asset_url + "/max/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(asset_url + "/start/10/max/20")
|
||||
last_date = self.check_page_content(resp.content, 20, last_date)
|
||||
resp = self.client.get(self.url + "?start=10&max=20", HTTP_ACCEPT='text/html')
|
||||
self.check_page_content(resp.content, 20, last_date)
|
||||
|
||||
@@ -585,29 +585,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
|
||||
'''
|
||||
This test will exercise the soft delete/restore functionality of the assets
|
||||
'''
|
||||
content_store = contentstore()
|
||||
trash_store = contentstore('trashcan')
|
||||
module_store = modulestore('direct')
|
||||
import_from_xml(module_store, 'common/test/data/', ['toy'], static_content_store=content_store)
|
||||
|
||||
# look up original (and thumbnail) in content store, should be there after import
|
||||
location = StaticContent.get_location_from_path('/c4x/edX/toy/asset/sample_static.txt')
|
||||
content = content_store.find(location, throw_on_not_found=False)
|
||||
thumbnail_location = content.thumbnail_location
|
||||
self.assertIsNotNone(content)
|
||||
|
||||
#
|
||||
# cdodge: temporarily comment out assertion on thumbnails because many environments
|
||||
# will not have the jpeg converter installed and this test will fail
|
||||
#
|
||||
# self.assertIsNotNone(thumbnail_location)
|
||||
|
||||
# go through the website to do the delete, since the soft-delete logic is in the view
|
||||
|
||||
url = reverse('update_asset', kwargs={'org': 'edX', 'course': 'toy', 'name': '2012_Fall', 'asset_id': '/c4x/edX/toy/asset/sample_static.txt'})
|
||||
resp = self.client.delete(url)
|
||||
self.assertEqual(resp.status_code, 204)
|
||||
|
||||
content_store, trash_store, thumbnail_location = self._delete_asset_in_course()
|
||||
asset_location = StaticContent.get_location_from_path('/c4x/edX/toy/asset/sample_static.txt')
|
||||
|
||||
# now try to find it in store, but they should not be there any longer
|
||||
@@ -637,29 +615,49 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
|
||||
thumbnail = content_store.find(thumbnail_location, throw_on_not_found=False)
|
||||
self.assertIsNotNone(thumbnail)
|
||||
|
||||
def _delete_asset_in_course (self):
|
||||
"""
|
||||
Helper method for:
|
||||
1) importing course from xml
|
||||
2) finding asset in course (verifying non-empty)
|
||||
3) computing thumbnail location of asset
|
||||
4) deleting the asset from the course
|
||||
"""
|
||||
|
||||
content_store = contentstore()
|
||||
trash_store = contentstore('trashcan')
|
||||
module_store = modulestore('direct')
|
||||
_, course_items = import_from_xml(module_store, 'common/test/data/', ['toy'], static_content_store=content_store)
|
||||
|
||||
# look up original (and thumbnail) in content store, should be there after import
|
||||
location = StaticContent.get_location_from_path('/c4x/edX/toy/asset/sample_static.txt')
|
||||
content = content_store.find(location, throw_on_not_found=False)
|
||||
thumbnail_location = content.thumbnail_location
|
||||
self.assertIsNotNone(content)
|
||||
|
||||
#
|
||||
# cdodge: temporarily comment out assertion on thumbnails because many environments
|
||||
# will not have the jpeg converter installed and this test will fail
|
||||
#
|
||||
# self.assertIsNotNone(thumbnail_location)
|
||||
|
||||
# go through the website to do the delete, since the soft-delete logic is in the view
|
||||
course = course_items[0]
|
||||
location = loc_mapper().translate_location(course.location.course_id, course.location, False, True)
|
||||
url = location.url_reverse('assets/', '/c4x/edX/toy/asset/sample_static.txt')
|
||||
resp = self.client.delete(url)
|
||||
self.assertEqual(resp.status_code, 204)
|
||||
|
||||
return content_store, trash_store, thumbnail_location
|
||||
|
||||
def test_empty_trashcan(self):
|
||||
'''
|
||||
This test will exercise the emptying of the asset trashcan
|
||||
'''
|
||||
content_store = contentstore()
|
||||
trash_store = contentstore('trashcan')
|
||||
module_store = modulestore('direct')
|
||||
|
||||
import_from_xml(module_store, 'common/test/data/', ['toy'], static_content_store=content_store)
|
||||
|
||||
course_location = CourseDescriptor.id_to_location('edX/toy/6.002_Spring_2012')
|
||||
|
||||
location = StaticContent.get_location_from_path('/c4x/edX/toy/asset/sample_static.txt')
|
||||
content = content_store.find(location, throw_on_not_found=False)
|
||||
self.assertIsNotNone(content)
|
||||
|
||||
# go through the website to do the delete, since the soft-delete logic is in the view
|
||||
|
||||
url = reverse('update_asset', kwargs={'org': 'edX', 'course': 'toy', 'name': '2012_Fall', 'asset_id': '/c4x/edX/toy/asset/sample_static.txt'})
|
||||
resp = self.client.delete(url)
|
||||
self.assertEqual(resp.status_code, 204)
|
||||
_, trash_store, _ = self._delete_asset_in_course()
|
||||
|
||||
# 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)
|
||||
self.assertGreater(len(all_assets), 0)
|
||||
|
||||
@@ -1633,11 +1631,11 @@ class ContentStoreTest(ModuleStoreTestCase):
|
||||
'name': loc.name}))
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
|
||||
# asset_index
|
||||
resp = self.client.get(reverse('asset_index',
|
||||
kwargs={'org': loc.org,
|
||||
'course': loc.course,
|
||||
'name': loc.name}))
|
||||
# assets_handler (HTML for full page content)
|
||||
new_location = loc_mapper().translate_location(loc.course_id, loc, False, True)
|
||||
url = new_location.url_reverse('assets/', '')
|
||||
|
||||
resp = self.client.get(url, HTTP_ACCEPT='text/html')
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
|
||||
# go look at a subsection page
|
||||
|
||||
@@ -5,7 +5,6 @@ from django.http import HttpResponseBadRequest
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.views.decorators.http import require_http_methods
|
||||
from django_future.csrf import ensure_csrf_cookie
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.views.decorators.http import require_POST
|
||||
|
||||
from mitxmako.shortcuts import render_to_response
|
||||
@@ -18,39 +17,67 @@ from xmodule.contentstore.content import StaticContent
|
||||
from xmodule.util.date_utils import get_default_time_display
|
||||
from xmodule.modulestore import InvalidLocationError
|
||||
from xmodule.exceptions import NotFoundError
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from xmodule.modulestore.django import loc_mapper
|
||||
from .access import has_access
|
||||
from xmodule.modulestore.locator import BlockUsageLocator
|
||||
|
||||
from .access import get_location_and_verify_access
|
||||
from util.json_request import JsonResponse
|
||||
from django.http import HttpResponseNotFound
|
||||
import json
|
||||
from django.utils.translation import ugettext as _
|
||||
from pymongo import DESCENDING
|
||||
|
||||
|
||||
__all__ = ['asset_index', 'upload_asset']
|
||||
__all__ = ['assets_handler']
|
||||
|
||||
|
||||
@login_required
|
||||
@ensure_csrf_cookie
|
||||
def asset_index(request, org, course, name, start=None, maxresults=None):
|
||||
def assets_handler(request, tag=None, course_id=None, branch=None, version_guid=None, block=None, asset_id=None):
|
||||
"""
|
||||
Display an editable asset library
|
||||
The restful handler for assets.
|
||||
It allows retrieval of all the assets (as an HTML page), as well as uploading new assets,
|
||||
deleting assets, and changing the "locked" state of an asset.
|
||||
|
||||
org, course, name: Attributes of the Location for the item to edit
|
||||
|
||||
:param start: which index of the result list to start w/, used for paging results
|
||||
:param maxresults: maximum results
|
||||
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
|
||||
POST
|
||||
json: create (or update?) an asset. The only updating that can be done is changing the lock state.
|
||||
PUT
|
||||
json: update the locked state of an asset
|
||||
DELETE
|
||||
json: delete an asset
|
||||
"""
|
||||
location = get_location_and_verify_access(request, org, course, name)
|
||||
location = BlockUsageLocator(course_id=course_id, branch=branch, version_guid=version_guid, usage_id=block)
|
||||
if not has_access(request.user, location):
|
||||
raise PermissionDenied()
|
||||
|
||||
upload_asset_callback_url = reverse('upload_asset', kwargs={
|
||||
'org': org,
|
||||
'course': course,
|
||||
'coursename': name
|
||||
})
|
||||
if 'application/json' in request.META.get('HTTP_ACCEPT', 'application/json'):
|
||||
if request.method == 'GET':
|
||||
raise NotImplementedError('coming soon')
|
||||
else:
|
||||
return _update_asset(request, location, asset_id)
|
||||
elif request.method == 'GET': # assume html
|
||||
return _asset_index(request, location)
|
||||
else:
|
||||
return HttpResponseNotFound()
|
||||
|
||||
course_module = modulestore().get_item(location)
|
||||
|
||||
course_reference = StaticContent.compute_location(org, course, name)
|
||||
def _asset_index(request, location):
|
||||
"""
|
||||
Display an editable asset library.
|
||||
|
||||
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)
|
||||
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
|
||||
@@ -77,36 +104,27 @@ def asset_index(request, org, course, name, start=None, maxresults=None):
|
||||
return render_to_response('asset_index.html', {
|
||||
'context_course': course_module,
|
||||
'asset_list': json.dumps(asset_json),
|
||||
'upload_asset_callback_url': upload_asset_callback_url,
|
||||
'update_asset_callback_url': reverse('update_asset', kwargs={
|
||||
'org': org,
|
||||
'course': course,
|
||||
'name': name
|
||||
})
|
||||
'asset_callback_url': location.url_reverse('assets/', '')
|
||||
})
|
||||
|
||||
|
||||
@require_POST
|
||||
@ensure_csrf_cookie
|
||||
@login_required
|
||||
def upload_asset(request, org, course, coursename):
|
||||
def _upload_asset(request, location):
|
||||
'''
|
||||
This method allows for POST uploading of files into the course asset
|
||||
library, which will be supported by GridFS in MongoDB.
|
||||
'''
|
||||
# construct a location from the passed in path
|
||||
location = get_location_and_verify_access(request, org, course, coursename)
|
||||
old_location = loc_mapper().translate_locator_to_location(location)
|
||||
|
||||
# Does the course actually exist?!? Get anything from it to prove its
|
||||
# existence
|
||||
try:
|
||||
modulestore().get_item(location)
|
||||
modulestore().get_item(old_location)
|
||||
except:
|
||||
# no return it as a Bad Request response
|
||||
logging.error('Could not find course' + location)
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
if 'file' not in request.FILES:
|
||||
logging.error('Could not find course' + old_location)
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
# compute a 'filename' which is similar to the location formatting, we're
|
||||
@@ -117,7 +135,7 @@ def upload_asset(request, org, course, coursename):
|
||||
filename = upload_file.name
|
||||
mime_type = upload_file.content_type
|
||||
|
||||
content_loc = StaticContent.compute_location(org, course, filename)
|
||||
content_loc = StaticContent.compute_location(old_location.org, old_location.course, filename)
|
||||
|
||||
chunked = upload_file.multiple_chunks()
|
||||
sc_partial = partial(StaticContent, content_loc, filename, mime_type)
|
||||
@@ -160,12 +178,11 @@ def upload_asset(request, org, course, coursename):
|
||||
@require_http_methods(("DELETE", "POST", "PUT"))
|
||||
@login_required
|
||||
@ensure_csrf_cookie
|
||||
def update_asset(request, org, course, name, asset_id):
|
||||
def _update_asset(request, location, asset_id):
|
||||
"""
|
||||
restful CRUD operations for a course asset.
|
||||
Currently only DELETE, POST, and PUT methods are implemented.
|
||||
|
||||
org, course, name: Attributes of the Location for the item to edit
|
||||
asset_id: the URL of the asset (used by Backbone as the id)
|
||||
"""
|
||||
def get_asset_location(asset_id):
|
||||
@@ -176,8 +193,6 @@ def update_asset(request, org, course, name, asset_id):
|
||||
# return a 'Bad Request' to browser as we have a malformed Location
|
||||
return JsonResponse({"error": err.message}, status=400)
|
||||
|
||||
get_location_and_verify_access(request, org, course, name)
|
||||
|
||||
if request.method == 'DELETE':
|
||||
loc = get_asset_location(asset_id)
|
||||
# Make sure the item to delete actually exists.
|
||||
@@ -208,16 +223,20 @@ def update_asset(request, org, course, name, asset_id):
|
||||
return JsonResponse()
|
||||
|
||||
elif request.method in ('PUT', 'POST'):
|
||||
# We don't support creation of new assets through this
|
||||
# method-- just changing the locked state.
|
||||
modified_asset = json.loads(request.body)
|
||||
asset_id = modified_asset['url']
|
||||
location = get_asset_location(asset_id)
|
||||
contentstore().set_attr(location, 'locked', modified_asset['locked'])
|
||||
# Delete the asset from the cache so we check the lock status the next time it is requested.
|
||||
del_cached_content(location)
|
||||
|
||||
return JsonResponse(modified_asset, status=201)
|
||||
if 'file' in request.FILES:
|
||||
return _upload_asset(request, location)
|
||||
else:
|
||||
# Update existing asset
|
||||
try:
|
||||
modified_asset = json.loads(request.body)
|
||||
except ValueError:
|
||||
return HttpResponseBadRequest()
|
||||
asset_id = modified_asset['url']
|
||||
asset_location = get_asset_location(asset_id)
|
||||
contentstore().set_attr(asset_location, 'locked', modified_asset['locked'])
|
||||
# Delete the asset from the cache so we check the lock status the next time it is requested.
|
||||
del_cached_content(asset_location)
|
||||
return JsonResponse(modified_asset, status=201)
|
||||
|
||||
|
||||
def _get_asset_json(display_name, date, location, thumbnail_location, locked):
|
||||
|
||||
@@ -124,12 +124,6 @@ def course_index(request, course_id, branch, version_guid, block):
|
||||
|
||||
lms_link = get_lms_link_for_item(old_location)
|
||||
|
||||
upload_asset_callback_url = reverse('upload_asset', kwargs={
|
||||
'org': old_location.org,
|
||||
'course': old_location.course,
|
||||
'coursename': old_location.name
|
||||
})
|
||||
|
||||
course = modulestore().get_item(old_location, depth=3)
|
||||
sections = course.get_children()
|
||||
|
||||
@@ -143,7 +137,6 @@ def course_index(request, course_id, branch, version_guid, block):
|
||||
'parent_location': course.location,
|
||||
'new_section_category': 'chapter',
|
||||
'new_subsection_category': 'sequential',
|
||||
'upload_asset_callback_url': upload_asset_callback_url,
|
||||
'new_unit_category': 'vertical',
|
||||
'category': 'vertical'
|
||||
})
|
||||
@@ -334,6 +327,9 @@ def get_course_settings(request, org, course, name):
|
||||
|
||||
course_module = modulestore().get_item(location)
|
||||
|
||||
new_loc = loc_mapper().translate_location(location.course_id, location, False, True)
|
||||
upload_asset_url = new_loc.url_reverse('assets/', '')
|
||||
|
||||
return render_to_response('settings.html', {
|
||||
'context_course': course_module,
|
||||
'course_location': location,
|
||||
@@ -345,11 +341,7 @@ def get_course_settings(request, org, course, name):
|
||||
'about_page_editable': not settings.MITX_FEATURES.get(
|
||||
'ENABLE_MKTG_SITE', False
|
||||
),
|
||||
'upload_asset_url': reverse('upload_asset', kwargs={
|
||||
'org': org,
|
||||
'course': course,
|
||||
'coursename': name,
|
||||
})
|
||||
'upload_asset_url': upload_asset_url
|
||||
})
|
||||
|
||||
|
||||
@@ -656,11 +648,8 @@ def textbook_index(request, org, course, name):
|
||||
)
|
||||
return JsonResponse(course_module.pdf_textbooks)
|
||||
else:
|
||||
upload_asset_url = reverse('upload_asset', kwargs={
|
||||
'org': org,
|
||||
'course': course,
|
||||
'coursename': name,
|
||||
})
|
||||
new_loc = loc_mapper().translate_location(location.course_id, location, False, True)
|
||||
upload_asset_url = new_loc.url_reverse('assets/', '')
|
||||
textbook_url = reverse('textbook_index', kwargs={
|
||||
'org': org,
|
||||
'course': course,
|
||||
|
||||
@@ -22,7 +22,7 @@ require(["domReady", "jquery", "gettext", "js/models/asset", "js/collections/ass
|
||||
function(domReady, $, gettext, AssetModel, AssetCollection, AssetsView, PromptView, NotificationView, ModalUtils) {
|
||||
|
||||
var assets = new AssetCollection(${asset_list});
|
||||
assets.url = "${update_asset_callback_url}";
|
||||
assets.url = "${asset_callback_url}";
|
||||
var assetsView = new AssetsView({collection: assets, el: $('#asset_table_body')});
|
||||
|
||||
var hideModal = function (e) {
|
||||
@@ -192,7 +192,7 @@ require(["domReady", "jquery", "gettext", "js/models/asset", "js/collections/ass
|
||||
<label>URL:</label>
|
||||
<input type="text" class="embeddable-xml-input" value='' readonly>
|
||||
</div>
|
||||
<form class="file-chooser" action="${upload_asset_callback_url}"
|
||||
<form class="file-chooser" action="${asset_callback_url}"
|
||||
method="post" enctype="multipart/form-data">
|
||||
<a href="#" class="choose-file-button">${_("Choose File")}</a>
|
||||
<input type="file" class="file-input" name="file" multiple>
|
||||
|
||||
@@ -24,7 +24,7 @@ CMS.URL.UPLOAD_ASSET = '${upload_asset_url}';
|
||||
|
||||
require(["domReady!", "jquery", "js/models/settings/course_details", "js/views/settings/main"],
|
||||
function(doc, $, CourseDetailsModel, MainView) {
|
||||
// hilighting labels when fields are focused in
|
||||
// highlighting labels when fields are focused in
|
||||
$("form :input").focus(function() {
|
||||
$("label[for='" + this.id + "']").addClass("is-focused");
|
||||
}).blur(function() {
|
||||
@@ -215,7 +215,7 @@ require(["domReady!", "jquery", "js/models/settings/course_details", "js/views/s
|
||||
</span>
|
||||
|
||||
<% ctx_loc = context_course.location %>
|
||||
<span class="msg msg-help">${_("You can manage this image along with all of your other")} <a href="${reverse('asset_index', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}">${_("files & uploads")}</a></span>
|
||||
<span class="msg msg-help">${_("You can manage this image along with all of your other")} <a href='${upload_asset_url}'>${_("files & uploads")}</a></span>
|
||||
|
||||
% else:
|
||||
<span class="wrapper-course-image">
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
index_url = location.url_reverse('course/', '')
|
||||
checklists_url = location.url_reverse('checklists/', '')
|
||||
course_team_url = location.url_reverse('course_team/', '')
|
||||
assets_url = location.url_reverse('assets/', '')
|
||||
%>
|
||||
<h2 class="info-course">
|
||||
<span class="sr">${_("Current Course:")}</span>
|
||||
@@ -47,7 +48,7 @@
|
||||
<a href="${reverse('edit_tabs', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, coursename=ctx_loc.name))}">${_("Static Pages")}</a>
|
||||
</li>
|
||||
<li class="nav-item nav-course-courseware-uploads">
|
||||
<a href="${reverse('asset_index', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}">${_("Files & Uploads")}</a>
|
||||
<a href="${assets_url}">${_("Files & Uploads")}</a>
|
||||
</li>
|
||||
<li class="nav-item nav-course-courseware-textbooks">
|
||||
<a href="${reverse('textbook_index', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}">${_("Textbooks")}</a>
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
<section>
|
||||
<div class="assset-upload">
|
||||
You can upload file assets (such as images) to reference in your courseware
|
||||
<form action="${upload_asset_callback_url}" method="post" enctype="multipart/form-data">
|
||||
<input type="file" name="file">
|
||||
<input type="submit" value="Upload File">
|
||||
</form>
|
||||
<div class="progress" style="position:relative; width:400px; border: 1px solid #ddd; padding: 1px; border-radius: 3px;">
|
||||
<div class="bar" style="background-color: #B4F5B4; width:0%; height:20px; border-radius: 3px;"></div>
|
||||
<div class="percent">0%</div>
|
||||
</div>
|
||||
|
||||
<div id="status"></div>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
<script>
|
||||
require(["domReady!", "jquery", "jquery.form"], function(doc, $) {
|
||||
|
||||
var bar = $('.bar');
|
||||
var percent = $('.percent');
|
||||
var status = $('#status');
|
||||
|
||||
$('form').ajaxForm({
|
||||
beforeSend: function() {
|
||||
status.empty();
|
||||
var percentVal = '0%';
|
||||
bar.width(percentVal)
|
||||
percent.html(percentVal);
|
||||
},
|
||||
uploadProgress: function(event, position, total, percentComplete) {
|
||||
var percentVal = percentComplete + '%';
|
||||
bar.width(percentVal)
|
||||
percent.html(percentVal);
|
||||
},
|
||||
complete: function(xhr) {
|
||||
status.html(xhr.responseText);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
</script>
|
||||
@@ -46,8 +46,6 @@ urlpatterns = patterns('', # nopep8
|
||||
|
||||
url(r'^preview/modx/(?P<preview_id>[^/]*)/(?P<location>.*?)/(?P<dispatch>[^/]*)$',
|
||||
'contentstore.views.preview_dispatch', name='preview_dispatch'),
|
||||
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/course/(?P<coursename>[^/]+)/upload_asset$',
|
||||
'contentstore.views.upload_asset', name='upload_asset'),
|
||||
|
||||
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/info/(?P<name>[^/]+)$',
|
||||
'contentstore.views.course_info', name='course_info'),
|
||||
@@ -74,10 +72,6 @@ urlpatterns = patterns('', # nopep8
|
||||
url(r'^edit_tabs/(?P<org>[^/]+)/(?P<course>[^/]+)/course/(?P<coursename>[^/]+)$',
|
||||
'contentstore.views.edit_tabs', name='edit_tabs'),
|
||||
|
||||
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/assets/(?P<name>[^/]+)(/start/(?P<start>\d+))?(/max/(?P<maxresults>\d+))?$',
|
||||
'contentstore.views.asset_index', name='asset_index'),
|
||||
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/assets/(?P<name>[^/]+)/(?P<asset_id>.+)?.*$',
|
||||
'contentstore.views.assets.update_asset', name='update_asset'),
|
||||
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/textbooks/(?P<name>[^/]+)$',
|
||||
'contentstore.views.textbook_index', name='textbook_index'),
|
||||
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/textbooks/(?P<name>[^/]+)/new$',
|
||||
@@ -134,7 +128,8 @@ urlpatterns += patterns(
|
||||
url(r'(?ix)^course/{}$'.format(parsers.URL_RE_SOURCE), 'course_handler'),
|
||||
url(r'(?ix)^checklists/{}(/)?(?P<checklist_index>\d+)?$'.format(parsers.URL_RE_SOURCE), 'checklists_handler'),
|
||||
url(r'(?ix)^course_team/{}(/)?(?P<email>.+)?$'.format(parsers.URL_RE_SOURCE), 'course_team_handler'),
|
||||
url(r'(?ix)^orphan/{}$'.format(parsers.URL_RE_SOURCE), 'orphan')
|
||||
url(r'(?ix)^orphan/{}$'.format(parsers.URL_RE_SOURCE), 'orphan'),
|
||||
url(r'(?ix)^assets/{}(/)?(?P<asset_id>.+)?$'.format(parsers.URL_RE_SOURCE), 'assets_handler')
|
||||
)
|
||||
|
||||
js_info_dict = {
|
||||
|
||||
Reference in New Issue
Block a user