Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
6d67d0c6
Commit
6d67d0c6
authored
Feb 05, 2014
by
Calen Pennington
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #1949 from cpennington/xblock-studio-js-and-css
Enable XBlock js and css in Studio
parents
7339f568
569c5def
Hide whitespace changes
Inline
Side-by-side
Showing
28 changed files
with
318 additions
and
190 deletions
+318
-190
cms/djangoapps/contentstore/tests/test_contentstore.py
+6
-3
cms/djangoapps/contentstore/tests/utils.py
+7
-0
cms/djangoapps/contentstore/views/item.py
+61
-22
cms/djangoapps/contentstore/views/preview.py
+13
-21
cms/lib/xblock/runtime.py
+1
-3
cms/static/coffee/spec/main.coffee
+1
-0
cms/static/coffee/spec/main_squire.coffee
+1
-0
cms/static/coffee/spec/views/module_edit_spec.coffee
+53
-4
cms/static/coffee/src/views/module_edit.coffee
+32
-4
cms/static/coffee/src/xblock/cms.runtime.v1.coffee
+22
-0
cms/static/js_test.yml
+2
-1
cms/static/js_test_squire.yml
+2
-0
cms/static/sass/elements/_xmodules.scss
+1
-1
cms/static/sass/views/_static-pages.scss
+3
-3
cms/static/sass/views/_unit.scss
+1
-1
common/djangoapps/xmodule_modifiers.py
+10
-6
common/static/coffee/spec/xblock/core_spec.coffee
+11
-8
common/static/coffee/spec/xblock/runtime.v1_spec.coffee
+2
-4
common/static/coffee/src/xblock/core.coffee
+5
-4
common/static/coffee/src/xblock/runtime.v1.coffee
+5
-24
lms/djangoapps/courseware/module_render.py
+4
-3
lms/djangoapps/instructor/views/instructor_dashboard.py
+1
-2
lms/djangoapps/instructor/views/legacy.py
+1
-2
lms/envs/common.py
+7
-5
lms/lib/xblock/runtime.py
+28
-58
lms/lib/xblock/test/test_runtime.py
+19
-11
lms/static/coffee/src/xblock/lms.runtime.v1.coffee
+18
-0
lms/static/js_test.yml
+1
-0
No files found.
cms/djangoapps/contentstore/tests/test_contentstore.py
View file @
6d67d0c6
#pylint: disable=E1101
import
shutil
import
json
import
mock
import
shutil
from
textwrap
import
dedent
...
...
@@ -503,7 +504,9 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
This verifies that a video caption url is as we expect it to be
"""
resp
=
self
.
_test_preview
(
Location
(
'i4x'
,
'edX'
,
'toy'
,
'video'
,
'sample_video'
,
None
))
self
.
assertContains
(
resp
,
'data-caption-asset-path="/c4x/edX/toy/asset/subs_"'
)
self
.
assertEquals
(
resp
.
status_code
,
200
)
content
=
json
.
loads
(
resp
.
content
)
self
.
assertIn
(
'data-caption-asset-path="/c4x/edX/toy/asset/subs_"'
,
content
[
'html'
])
def
_test_preview
(
self
,
location
):
""" Preview test case. """
...
...
@@ -514,7 +517,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
locator
=
loc_mapper
()
.
translate_location
(
course_items
[
0
]
.
location
.
course_id
,
location
,
True
,
True
)
resp
=
self
.
client
.
get_
html
(
locator
.
url_reverse
(
'xblock'
))
resp
=
self
.
client
.
get_
fragment
(
locator
.
url_reverse
(
'xblock'
))
self
.
assertEqual
(
resp
.
status_code
,
200
)
# TODO: uncomment when preview no longer has locations being returned.
# _test_no_locations(self, resp)
...
...
cms/djangoapps/contentstore/tests/utils.py
View file @
6d67d0c6
...
...
@@ -57,6 +57,13 @@ class AjaxEnabledTestClient(Client):
"""
return
self
.
get
(
path
,
data
or
{},
follow
,
HTTP_ACCEPT
=
"application/json"
,
**
extra
)
def
get_fragment
(
self
,
path
,
data
=
None
,
follow
=
False
,
**
extra
):
"""
Convenience method for client.get which sets the accept type to application/x-fragment+json
"""
return
self
.
get
(
path
,
data
or
{},
follow
,
HTTP_ACCEPT
=
"application/x-fragment+json"
,
**
extra
)
@override_settings
(
MODULESTORE
=
TEST_MODULESTORE
)
class
CourseTestCase
(
ModuleStoreTestCase
):
...
...
cms/djangoapps/contentstore/views/item.py
View file @
6d67d0c6
"""Views for items (modules)."""
import
hashlib
import
logging
from
uuid
import
uuid4
from
collections
import
OrderedDict
from
functools
import
partial
from
static_replace
import
replace_static_urls
from
xmodule_modifiers
import
wrap_xblock
from
django.conf
import
settings
from
django.core.exceptions
import
PermissionDenied
from
django.contrib.auth.decorators
import
login_required
from
django.http
import
HttpResponseBadRequest
from
django.http
import
HttpResponseBadRequest
,
HttpResponse
from
django.utils.translation
import
ugettext
as
_
from
django.views.decorators.http
import
require_http_methods
from
xblock.fields
import
Scope
from
xblock.fragment
import
Fragment
from
xblock.core
import
XBlock
import
xmodule.x_module
from
xmodule.modulestore.django
import
modulestore
,
loc_mapper
from
xmodule.modulestore.exceptions
import
ItemNotFoundError
,
InvalidLocationError
from
xmodule.modulestore.inheritance
import
own_metadata
from
xmodule.modulestore.locator
import
BlockUsageLocator
from
xmodule.modulestore
import
Location
from
xmodule.x_module
import
prefer_xmodules
from
util.json_request
import
expect_json
,
JsonResponse
from
util.string_utils
import
str_to_bool
...
...
@@ -31,10 +36,10 @@ from ..utils import get_modulestore
from
.access
import
has_course_access
from
.helpers
import
_xmodule_recurse
from
preview
import
handler_prefix
,
get_preview_html
from
edxmako.shortcuts
import
render_to_
response
,
render_to_
string
from
contentstore.views.preview
import
get_preview_fragment
from
edxmako.shortcuts
import
render_to_string
from
models.settings.course_grading
import
CourseGradingModel
from
django.utils.translation
import
ugettext
as
_
from
cms.lib.xblock.runtime
import
handler_url
__all__
=
[
'orphan_handler'
,
'xblock_handler'
]
...
...
@@ -43,6 +48,22 @@ log = logging.getLogger(__name__)
CREATE_IF_NOT_FOUND
=
[
'course_info'
]
# In order to allow descriptors to use a handler url, we need to
# monkey-patch the x_module library.
# TODO: Remove this code when Runtimes are no longer created by modulestores
xmodule
.
x_module
.
descriptor_global_handler_url
=
handler_url
def
hash_resource
(
resource
):
"""
Hash a :class:`xblock.fragment.FragmentResource
"""
md5
=
hashlib
.
md5
()
for
data
in
resource
:
md5
.
update
(
data
)
return
md5
.
hexdigest
()
# pylint: disable=unused-argument
@require_http_methods
((
"DELETE"
,
"GET"
,
"PUT"
,
"POST"
))
@login_required
...
...
@@ -88,34 +109,52 @@ def xblock_handler(request, tag=None, package_id=None, branch=None, version_guid
old_location
=
loc_mapper
()
.
translate_locator_to_location
(
locator
)
if
request
.
method
==
'GET'
:
if
'application/json'
in
request
.
META
.
get
(
'HTTP_ACCEPT'
,
'application/json'
):
fields
=
request
.
REQUEST
.
get
(
'fields'
,
''
)
.
split
(
','
)
if
'graderType'
in
fields
:
# right now can't combine output of this w/ output of _get_module_info, but worthy goal
return
JsonResponse
(
CourseGradingModel
.
get_section_grader_type
(
locator
))
# TODO: pass fields to _get_module_info and only return those
rsp
=
_get_module_info
(
locator
)
return
JsonResponse
(
rsp
)
else
:
accept_header
=
request
.
META
.
get
(
'HTTP_ACCEPT'
,
'application/json'
)
if
'application/x-fragment+json'
in
accept_header
:
component
=
modulestore
()
.
get_item
(
old_location
)
# Wrap the generated fragment in the xmodule_editor div so that the javascript
# can bind to it correctly
component
.
runtime
.
wrappers
.
append
(
partial
(
wrap_xblock
,
handler_prefix
))
component
.
runtime
.
wrappers
.
append
(
partial
(
wrap_xblock
,
'StudioRuntime'
))
try
:
content
=
component
.
render
(
'studio_view'
)
.
content
editor_fragment
=
component
.
render
(
'studio_view'
)
# catch exceptions indiscriminately, since after this point they escape the
# dungeon and surface as uneditable, unsaveable, and undeletable
# component-goblins.
except
Exception
as
exc
:
# pylint: disable=W0703
log
.
debug
(
"Unable to render studio_view for
%
r"
,
component
,
exc_info
=
True
)
content
=
render_to_string
(
'html_error.html'
,
{
'message'
:
str
(
exc
)})
editor_fragment
=
Fragment
(
render_to_string
(
'html_error.html'
,
{
'message'
:
str
(
exc
)}))
modulestore
()
.
save_xmodule
(
component
)
preview_fragment
=
get_preview_fragment
(
request
,
component
)
return
render_to_response
(
'component.html'
,
{
'preview'
:
get_preview_html
(
request
,
component
),
'editor'
:
content
,
'label'
:
component
.
display_name
or
component
.
category
,
hashed_resources
=
OrderedDict
()
for
resource
in
editor_fragment
.
resources
+
preview_fragment
.
resources
:
hashed_resources
[
hash_resource
(
resource
)]
=
resource
return
JsonResponse
({
'html'
:
render_to_string
(
'component.html'
,
{
'preview'
:
preview_fragment
.
content
,
'editor'
:
editor_fragment
.
content
,
'label'
:
component
.
display_name
or
component
.
scope_ids
.
block_type
,
}),
'resources'
:
hashed_resources
.
items
()
})
elif
'application/json'
in
accept_header
:
fields
=
request
.
REQUEST
.
get
(
'fields'
,
''
)
.
split
(
','
)
if
'graderType'
in
fields
:
# right now can't combine output of this w/ output of _get_module_info, but worthy goal
return
JsonResponse
(
CourseGradingModel
.
get_section_grader_type
(
locator
))
# TODO: pass fields to _get_module_info and only return those
rsp
=
_get_module_info
(
locator
)
return
JsonResponse
(
rsp
)
else
:
return
HttpResponse
(
status
=
406
)
elif
request
.
method
==
'DELETE'
:
delete_children
=
str_to_bool
(
request
.
REQUEST
.
get
(
'recurse'
,
'False'
))
delete_all_versions
=
str_to_bool
(
request
.
REQUEST
.
get
(
'all_versions'
,
'False'
))
...
...
@@ -281,7 +320,7 @@ def _create_item(request):
data
=
None
template_id
=
request
.
json
.
get
(
'boilerplate'
)
if
template_id
is
not
None
:
clz
=
XBlock
.
load_class
(
category
,
select
=
prefer_xmodules
)
clz
=
parent
.
runtime
.
load_block_type
(
category
)
if
clz
is
not
None
:
template
=
clz
.
get_template
(
template_id
)
if
template
is
not
None
:
...
...
cms/djangoapps/contentstore/views/preview.py
View file @
6d67d0c6
import
logging
import
hashlib
from
functools
import
partial
from
django.conf
import
settings
from
django.core.urlresolvers
import
reverse
from
django.http
import
Http404
,
HttpResponseBadRequest
from
django.contrib.auth.decorators
import
login_required
from
edxmako.shortcuts
import
render_to_
response
,
render_to_
string
from
edxmako.shortcuts
import
render_to_string
from
xmodule_modifiers
import
replace_static_urls
,
wrap_xblock
from
xmodule.error_module
import
ErrorDescriptor
...
...
@@ -15,6 +16,7 @@ from xmodule.x_module import ModuleSystem
from
xblock.runtime
import
KvsFieldData
from
xblock.django.request
import
webob_to_django_response
,
django_to_webob_request
from
xblock.exceptions
import
NoSuchHandlerError
from
xblock.fragment
import
Fragment
from
lms.lib.xblock.field_data
import
LmsFieldData
from
lms.lib.xblock.runtime
import
quote_slashes
,
unquote_slashes
...
...
@@ -33,20 +35,6 @@ __all__ = ['preview_handler']
log
=
logging
.
getLogger
(
__name__
)
def
handler_prefix
(
block
,
handler
=
''
,
suffix
=
''
):
"""
Return a url prefix for XBlock handler_url. The full handler_url
should be '{prefix}/{handler}/{suffix}?{query}'.
Trailing `/`s are removed from the returned url.
"""
return
reverse
(
'preview_handler'
,
kwargs
=
{
'usage_id'
:
quote_slashes
(
unicode
(
block
.
scope_ids
.
usage_id
)
.
encode
(
'utf-8'
)),
'handler'
:
handler
,
'suffix'
:
suffix
,
})
.
rstrip
(
'/?'
)
@login_required
def
preview_handler
(
request
,
usage_id
,
handler
,
suffix
=
''
):
"""
...
...
@@ -91,7 +79,11 @@ class PreviewModuleSystem(ModuleSystem): # pylint: disable=abstract-method
An XModule ModuleSystem for use in Studio previews
"""
def
handler_url
(
self
,
block
,
handler_name
,
suffix
=
''
,
query
=
''
,
thirdparty
=
False
):
return
handler_prefix
(
block
,
handler_name
,
suffix
)
+
'?'
+
query
return
reverse
(
'preview_handler'
,
kwargs
=
{
'usage_id'
:
quote_slashes
(
unicode
(
block
.
scope_ids
.
usage_id
)
.
encode
(
'utf-8'
)),
'handler'
:
handler_name
,
'suffix'
:
suffix
,
})
+
'?'
+
query
def
_preview_module_system
(
request
,
descriptor
):
...
...
@@ -123,7 +115,7 @@ def _preview_module_system(request, descriptor):
# Set up functions to modify the fragment produced by student_view
wrappers
=
(
# This wrapper wraps the module in the template specified above
partial
(
wrap_xblock
,
handler_prefix
,
display_name_only
=
descriptor
.
location
.
category
==
'static_tab'
),
partial
(
wrap_xblock
,
'PreviewRuntime'
,
display_name_only
=
descriptor
.
location
.
category
==
'static_tab'
),
# This wrapper replaces urls in the output that start with /static
# with the correct course-specific url for the static content
...
...
@@ -153,15 +145,15 @@ def _load_preview_module(request, descriptor):
return
descriptor
def
get_preview_
html
(
request
,
descriptor
):
def
get_preview_
fragment
(
request
,
descriptor
):
"""
Returns the HTML returned by the XModule's student_view,
specified by the descriptor and idx.
"""
module
=
_load_preview_module
(
request
,
descriptor
)
try
:
content
=
module
.
render
(
"student_view"
)
.
content
fragment
=
module
.
render
(
"student_view"
)
except
Exception
as
exc
:
# pylint: disable=W0703
log
.
debug
(
"Unable to render student_view for
%
r"
,
module
,
exc_info
=
True
)
content
=
render_to_string
(
'html_error.html'
,
{
'message'
:
str
(
exc
)}
)
return
cont
ent
fragment
=
Fragment
(
render_to_string
(
'html_error.html'
,
{
'message'
:
str
(
exc
)})
)
return
fragm
ent
cms/lib/xblock/runtime.py
View file @
6d67d0c6
...
...
@@ -4,7 +4,6 @@ XBlock runtime implementations for edX Studio
from
django.core.urlresolvers
import
reverse
import
xmodule.x_module
from
lms.lib.xblock.runtime
import
quote_slashes
...
...
@@ -17,7 +16,7 @@ def handler_url(block, handler_name, suffix='', query='', thirdparty=False):
raise
NotImplementedError
(
"edX Studio doesn't support third-party xblock handler urls"
)
url
=
reverse
(
'component_handler'
,
kwargs
=
{
'usage_id'
:
quote_slashes
(
str
(
block
.
scope_ids
.
usage_id
)),
'usage_id'
:
quote_slashes
(
unicode
(
block
.
scope_ids
.
usage_id
)
.
encode
(
'utf-8'
)),
'handler'
:
handler_name
,
'suffix'
:
suffix
,
})
.
rstrip
(
'/'
)
...
...
@@ -27,4 +26,3 @@ def handler_url(block, handler_name, suffix='', query='', thirdparty=False):
return
url
xmodule
.
x_module
.
descriptor_global_handler_url
=
handler_url
cms/static/coffee/spec/main.coffee
View file @
6d67d0c6
...
...
@@ -28,6 +28,7 @@ requirejs.config({
"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"
,
"xblock/cms.runtime.v1"
:
"coffee/src/xblock/cms.runtime.v1"
,
"xblock"
:
"xmodule_js/common_static/coffee/src/xblock"
,
"utility"
:
"xmodule_js/common_static/js/src/utility"
,
"accessibility"
:
"xmodule_js/common_static/js/src/accessibility_tools"
,
...
...
cms/static/coffee/spec/main_squire.coffee
View file @
6d67d0c6
...
...
@@ -27,6 +27,7 @@ requirejs.config({
"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"
,
"xblock/cms.runtime.v1"
:
"coffee/src/xblock/cms.runtime.v1"
,
"xblock"
:
"xmodule_js/common_static/coffee/src/xblock"
,
"utility"
:
"xmodule_js/common_static/js/src/utility"
,
"sinon"
:
"xmodule_js/common_static/js/vendor/sinon-1.7.1"
,
...
...
cms/static/coffee/spec/views/module_edit_spec.coffee
View file @
6d67d0c6
define
[
"
coffee/src/views/module_edit"
,
"js/models/module_info"
,
"xmodule"
],
(
ModuleEdit
,
ModuleModel
)
->
define
[
"
jquery"
,
"coffee/src/views/module_edit"
,
"js/models/module_info"
,
"xmodule"
],
(
$
,
ModuleEdit
,
ModuleModel
)
->
describe
"ModuleEdit"
,
->
beforeEach
->
...
...
@@ -24,7 +24,7 @@ define ["coffee/src/views/module_edit", "js/models/module_info", "xmodule"], (Mo
</section>
</li>
"""
spyOn
(
$
.
fn
,
'load
'
).
andReturn
(
@
moduleData
)
spyOn
(
$
,
'ajax
'
).
andReturn
(
@
moduleData
)
@
moduleEdit
=
new
ModuleEdit
(
el
:
$
(
".component"
)
...
...
@@ -56,14 +56,63 @@ define ["coffee/src/views/module_edit", "js/models/module_info", "xmodule"], (Mo
beforeEach
->
spyOn
(
@
moduleEdit
,
'loadDisplay'
)
spyOn
(
@
moduleEdit
,
'delegateEvents'
)
spyOn
(
$
.
fn
,
'append'
)
spyOn
(
$
,
'getScript'
)
window
.
loadedXBlockResources
=
undefined
@
moduleEdit
.
render
()
$
.
ajax
.
mostRecentCall
.
args
[
0
].
success
(
html
:
'<div>Response html</div>'
resources
:
[
[
'hash1'
,
{
kind
:
'text'
,
mimetype
:
'text/css'
,
data
:
'inline-css'
}],
[
'hash2'
,
{
kind
:
'url'
,
mimetype
:
'text/css'
,
data
:
'css-url'
}],
[
'hash3'
,
{
kind
:
'text'
,
mimetype
:
'application/javascript'
,
data
:
'inline-js'
}],
[
'hash4'
,
{
kind
:
'url'
,
mimetype
:
'application/javascript'
,
data
:
'js-url'
}],
[
'hash5'
,
{
placement
:
'head'
,
mimetype
:
'text/html'
,
data
:
'head-html'
}],
[
'hash6'
,
{
placement
:
'not-head'
,
mimetype
:
'text/html'
,
data
:
'not-head-html'
}],
]
)
it
"loads the module preview and editor via ajax on the view element"
,
->
expect
(
@
moduleEdit
.
$el
.
load
).
toHaveBeenCalledWith
(
"/xblock/
#{
@
moduleEdit
.
model
.
id
}
"
,
jasmine
.
any
(
Function
))
@
moduleEdit
.
$el
.
load
.
mostRecentCall
.
args
[
1
]()
expect
(
$
.
ajax
).
toHaveBeenCalledWith
(
url
:
"/xblock/
#{
@
moduleEdit
.
model
.
id
}
"
type
:
"GET"
headers
:
Accept
:
'application/x-fragment+json'
success
:
jasmine
.
any
(
Function
)
)
expect
(
@
moduleEdit
.
loadDisplay
).
toHaveBeenCalled
()
expect
(
@
moduleEdit
.
delegateEvents
).
toHaveBeenCalled
()
it
"loads inline css from fragments"
,
->
expect
(
$
(
'head'
).
append
).
toHaveBeenCalledWith
(
"<style type='text/css'>inline-css</style>"
)
it
"loads css urls from fragments"
,
->
expect
(
$
(
'head'
).
append
).
toHaveBeenCalledWith
(
"<link rel='stylesheet' href='css-url' type='text/css'>"
)
it
"loads inline js from fragments"
,
->
expect
(
$
(
'head'
).
append
).
toHaveBeenCalledWith
(
"<script>inline-js</script>"
)
it
"loads js urls from fragments"
,
->
expect
(
$
.
getScript
).
toHaveBeenCalledWith
(
"js-url"
)
it
"loads head html"
,
->
expect
(
$
(
'head'
).
append
).
toHaveBeenCalledWith
(
"head-html"
)
it
"doesn't load body html"
,
->
expect
(
$
.
fn
.
append
).
not
.
toHaveBeenCalledWith
(
'not-head-html'
)
it
"doesn't reload resources"
,
->
count
=
$
(
'head'
).
append
.
callCount
$
.
ajax
.
mostRecentCall
.
args
[
0
].
success
(
html
:
'<div>Response html 2</div>'
resources
:
[
[
'hash1'
,
{
kind
:
'text'
,
mimetype
:
'text/css'
,
data
:
'inline-css'
}],
]
)
expect
(
$
(
'head'
).
append
.
callCount
).
toBe
(
count
)
describe
"loadDisplay"
,
->
beforeEach
->
spyOn
(
XBlock
,
'initializeBlock'
)
...
...
cms/static/coffee/src/views/module_edit.coffee
View file @
6d67d0c6
define
[
"backbone"
,
"jquery"
,
"underscore"
,
"gettext"
,
"xblock/runtime.v1"
,
"js/views/feedback_notification"
,
"js/views/metadata"
,
"js/collections/metadata"
"js/utils/modal"
,
"jquery.inputnumber"
,
"xmodule"
,
"coffee/src/main"
],
"js/utils/modal"
,
"jquery.inputnumber"
,
"xmodule"
,
"coffee/src/main"
,
"xblock/cms.runtime.v1"
],
(
Backbone
,
$
,
_
,
gettext
,
XBlock
,
NotificationView
,
MetadataView
,
MetadataCollection
,
ModalUtils
)
->
class
ModuleEdit
extends
Backbone
.
View
tagName
:
'li'
...
...
@@ -75,9 +75,37 @@ define ["backbone", "jquery", "underscore", "gettext", "xblock/runtime.v1",
render
:
->
if
@
model
.
id
@
$el
.
load
(
@
model
.
url
(),
=>
@
loadDisplay
()
@
delegateEvents
()
$
.
ajax
(
url
:
@
model
.
url
()
type
:
'GET'
headers
:
Accept
:
'application/x-fragment+json'
success
:
(
data
)
=>
@
$el
.
html
(
data
.
html
)
for
value
in
data
.
resources
do
(
value
)
=>
hash
=
value
[
0
]
if
not
window
.
loadedXBlockResources
?
window
.
loadedXBlockResources
=
[]
if
hash
not
in
window
.
loadedXBlockResources
resource
=
value
[
1
]
switch
resource
.
mimetype
when
"text/css"
switch
resource
.
kind
when
"text"
then
$
(
'head'
).
append
(
"<style type='text/css'>
#{
resource
.
data
}
</style>"
)
when
"url"
then
$
(
'head'
).
append
(
"<link rel='stylesheet' href='
#{
resource
.
data
}
' type='text/css'>"
)
when
"application/javascript"
switch
resource
.
kind
when
"text"
then
$
(
'head'
).
append
(
"<script>
#{
resource
.
data
}
</script>"
)
when
"url"
then
$
.
getScript
(
resource
.
data
)
when
"text/html"
switch
resource
.
placement
when
"head"
then
$
(
'head'
).
append
(
resource
.
data
)
window
.
loadedXBlockResources
.
push
(
hash
)
@
loadDisplay
()
@
delegateEvents
()
)
clickSaveButton
:
(
event
)
=>
...
...
cms/static/coffee/src/xblock/cms.runtime.v1.coffee
0 → 100644
View file @
6d67d0c6
define
[
"jquery"
,
"xblock/runtime.v1"
,
"URI"
],
(
$
,
XBlock
,
URI
)
->
@
PreviewRuntime
=
{}
class
PreviewRuntime
.
v1
extends
XBlock
.
Runtime
.
v1
handlerUrl
:
(
element
,
handlerName
,
suffix
,
query
,
thirdparty
)
->
uri
=
URI
(
"/preview/xblock"
).
segment
(
$
(
@
element
).
data
(
'usage-id'
))
.
segment
(
'handler'
)
.
segment
(
handlerName
)
if
suffix
?
then
uri
.
segment
(
suffix
)
if
query
?
then
uri
.
search
(
query
)
uri
.
toString
()
@
StudioRuntime
=
{}
class
StudioRuntime
.
v1
extends
XBlock
.
Runtime
.
v1
handlerUrl
:
(
element
,
handlerName
,
suffix
,
query
,
thirdparty
)
->
uri
=
URI
(
"/xblock"
).
segment
(
$
(
@
element
).
data
(
'usage-id'
))
.
segment
(
'handler'
)
.
segment
(
handlerName
)
if
suffix
?
then
uri
.
segment
(
suffix
)
if
query
?
then
uri
.
search
(
query
)
uri
.
toString
()
cms/static/js_test.yml
View file @
6d67d0c6
...
...
@@ -59,7 +59,8 @@ lib_paths:
-
xmodule_js/common_static/js/vendor/URI.min.js
-
xmodule_js/common_static/js/vendor/jquery.smooth-scroll.min.js
-
xmodule_js/common_static/coffee/src/jquery.immediateDescendents.js
-
xmodule_js/common_static/coffee/src/xblock
-
xmodule_js/common_static/coffee/src/xblock/
-
xmodule_js/common_static/js/vendor/URI.min.js
# Paths to source JavaScript files
src_paths
:
...
...
cms/static/js_test_squire.yml
View file @
6d67d0c6
...
...
@@ -54,6 +54,8 @@ lib_paths:
-
xmodule_js/src/xmodule.js
-
xmodule_js/common_static/coffee/src/jquery.immediateDescendents.js
-
xmodule_js/common_static/js/test/i18n.js
-
xmodule_js/common_static/coffee/src/xblock/
-
xmodule_js/common_static/js/vendor/URI.min.js
# Paths to source JavaScript files
src_paths
:
...
...
cms/static/sass/elements/_xmodules.scss
View file @
6d67d0c6
...
...
@@ -16,7 +16,7 @@
.xmodule_VideoModule
{
// display mode
&
.x
module_display
{
&
.x
block-student_view
{
// full screen
.video-controls
.add-fullscreen
{
...
...
cms/static/sass/views/_static-pages.scss
View file @
6d67d0c6
...
...
@@ -183,16 +183,16 @@
border-left
:
1px
solid
$mediumGrey
;
border-right
:
1px
solid
$mediumGrey
;
.x
module_display
{
.x
block-student_view
{
display
:
none
;
}
}
.new
.x
module_display
{
.new
.x
block-student_view
{
background
:
$yellow
;
}
.x
module_display
{
.x
block-student_view
{
@include
transition
(
background-color
$tmg-s3
linear
0s
);
padding
:
20px
20px
22px
;
font-size
:
24px
;
...
...
cms/static/sass/views/_unit.scss
View file @
6d67d0c6
...
...
@@ -420,7 +420,7 @@ body.course.unit,.view-unit {
}
}
.x
module_display
{
.x
block-student_view
{
padding
:
2
*
$baseline
$baseline
$baseline
;
overflow-x
:
auto
;
...
...
common/djangoapps/xmodule_modifiers.py
View file @
6d67d0c6
...
...
@@ -15,6 +15,7 @@ from xblock.fragment import Fragment
from
xmodule.seq_module
import
SequenceModule
from
xmodule.vertical_module
import
VerticalModule
from
xmodule.x_module
import
shim_xmodule_js
,
XModuleDescriptor
,
XModule
from
lms.lib.xblock.runtime
import
quote_slashes
log
=
logging
.
getLogger
(
__name__
)
...
...
@@ -29,26 +30,28 @@ def wrap_fragment(fragment, new_content):
return
wrapper_frag
def
wrap_xblock
(
handler_prefix
,
block
,
view
,
frag
,
context
,
display_name_only
=
Fals
e
):
# pylint: disable=unused-argument
def
wrap_xblock
(
runtime_class
,
block
,
view
,
frag
,
context
,
display_name_only
=
False
,
extra_data
=
Non
e
):
# pylint: disable=unused-argument
"""
Wraps the results of rendering an XBlock view in a standard <section> with identifying
data so that the appropriate javascript module can be loaded onto it.
:param handler_prefix: A function that takes a block and returns the url prefix for
the javascript handler_url. This prefix should be able to have {handler_name}/{suffix}?{query}
appended to it to return a valid handler_url
:param runtime_class: The name of the javascript runtime class to use to load this block
:param block: An XBlock (that may be an XModule or XModuleDescriptor)
:param view: The name of the view that rendered the fragment being wrapped
:param frag: The :class:`Fragment` to be wrapped
:param context: The context passed to the view being rendered
:param display_name_only: If true, don't render the fragment content at all.
Instead, just render the `display_name` of `block`
:param extra_data: A dictionary with extra data values to be set on the wrapper
"""
if
extra_data
is
None
:
extra_data
=
{}
# If any mixins have been applied, then use the unmixed class
class_name
=
getattr
(
block
,
'unmixed_class'
,
block
.
__class__
)
.
__name__
data
=
{}
data
.
update
(
extra_data
)
css_classes
=
[
'xblock'
,
'xblock-'
+
view
]
if
isinstance
(
block
,
(
XModule
,
XModuleDescriptor
)):
...
...
@@ -65,14 +68,15 @@ def wrap_xblock(handler_prefix, block, view, frag, context, display_name_only=Fa
if
frag
.
js_init_fn
:
data
[
'init'
]
=
frag
.
js_init_fn
data
[
'runtime-class'
]
=
runtime_class
data
[
'runtime-version'
]
=
frag
.
js_init_version
data
[
'handler-prefix'
]
=
handler_prefix
(
block
)
data
[
'block-type'
]
=
block
.
scope_ids
.
block_type
data
[
'usage-id'
]
=
quote_slashes
(
unicode
(
block
.
scope_ids
.
usage_id
)
.
encode
(
'utf-8'
))
template_context
=
{
'content'
:
block
.
display_name
if
display_name_only
else
frag
.
content
,
'classes'
:
css_classes
,
'data_attributes'
:
' '
.
join
(
'data-{}="{}"'
.
format
(
key
,
value
)
for
key
,
value
in
data
.
items
()),
'data_attributes'
:
' '
.
join
(
u
'data-{}="{}"'
.
format
(
key
,
value
)
for
key
,
value
in
data
.
items
()),
}
return
wrap_fragment
(
frag
,
render_to_string
(
'xblock_wrapper.html'
,
template_context
))
...
...
common/static/coffee/spec/xblock/core_spec.coffee
View file @
6d67d0c6
...
...
@@ -2,9 +2,9 @@ describe "XBlock", ->
beforeEach
->
setFixtures
"""
<div>
<div class='xblock' id='vA' data-runtime-version="A" data-init="initFnA" data-name="a-name"/>
<div class='xblock' id='vA' data-runtime-version="A" data-
runtime-class="TestRuntime" data-
init="initFnA" data-name="a-name"/>
<div>
<div class='xblock' id='vZ' data-runtime-version="Z" data-init="initFnZ"/>
<div class='xblock' id='vZ' data-runtime-version="Z" data-
runtime-class="TestRuntime" data-
init="initFnZ"/>
</div>
<div class='xblock' id='missing-version' data-init='initFnA' data-name='no-version'/>
<div class='xblock' id='missing-init' data-runtime-version="A" data-name='no-init'/>
...
...
@@ -13,8 +13,11 @@ describe "XBlock", ->
describe
"initializeBlock"
,
->
beforeEach
->
XBlock
.
runtime
.
vA
=
jasmine
.
createSpy
().
andReturn
(
'runtimeA'
)
XBlock
.
runtime
.
vZ
=
jasmine
.
createSpy
().
andReturn
(
'runtimeZ'
)
window
.
TestRuntime
=
{}
@
runtimeA
=
{
name
:
'runtimeA'
}
@
runtimeZ
=
{
name
:
'runtimeZ'
}
TestRuntime
.
vA
=
jasmine
.
createSpy
().
andReturn
(
@
runtimeA
)
TestRuntime
.
vZ
=
jasmine
.
createSpy
().
andReturn
(
@
runtimeZ
)
window
.
initFnA
=
jasmine
.
createSpy
()
window
.
initFnZ
=
jasmine
.
createSpy
()
...
...
@@ -28,12 +31,12 @@ describe "XBlock", ->
@
missingInitBlock
=
XBlock
.
initializeBlock
(
$
(
'#missing-init'
)[
0
])
it
"loads the right runtime version"
,
->
expect
(
XBlock
.
r
untime
.
vA
).
toHaveBeenCalledWith
(
$
(
'#vA'
)[
0
],
@
fakeChildren
)
expect
(
XBlock
.
r
untime
.
vZ
).
toHaveBeenCalledWith
(
$
(
'#vZ'
)[
0
],
@
fakeChildren
)
expect
(
TestR
untime
.
vA
).
toHaveBeenCalledWith
(
$
(
'#vA'
)[
0
],
@
fakeChildren
)
expect
(
TestR
untime
.
vZ
).
toHaveBeenCalledWith
(
$
(
'#vZ'
)[
0
],
@
fakeChildren
)
it
"loads the right init function"
,
->
expect
(
window
.
initFnA
).
toHaveBeenCalledWith
(
'runtimeA'
,
$
(
'#vA'
)[
0
])
expect
(
window
.
initFnZ
).
toHaveBeenCalledWith
(
'runtimeZ'
,
$
(
'#vZ'
)[
0
])
expect
(
window
.
initFnA
).
toHaveBeenCalledWith
(
@
runtimeA
,
$
(
'#vA'
)[
0
])
expect
(
window
.
initFnZ
).
toHaveBeenCalledWith
(
@
runtimeZ
,
$
(
'#vZ'
)[
0
])
it
"loads when missing versions"
,
->
expect
(
@
missingVersionBlock
.
element
).
toBe
(
$
(
'#missing-version'
))
...
...
common/static/coffee/spec/xblock/runtime.v1_spec.coffee
View file @
6d67d0c6
describe
"XBlock.
r
untime.v1"
,
->
describe
"XBlock.
R
untime.v1"
,
->
beforeEach
->
setFixtures
"""
<div class='xblock' data-handler-prefix='/xblock/fake-usage-id/handler'/>
...
...
@@ -10,9 +10,7 @@ describe "XBlock.runtime.v1", ->
@
element
=
$
(
'.xblock'
)[
0
]
@
runtime
=
XBlock
.
runtime
.
v1
(
@
element
,
@
children
)
it
"provides a handler url"
,
->
expect
(
@
runtime
.
handlerUrl
(
@
element
,
'foo'
)).
toBe
(
'/xblock/fake-usage-id/handler/foo'
)
@
runtime
=
new
XBlock
.
Runtime
.
v1
(
@
element
,
@
children
)
it
"provides a list of children"
,
->
expect
(
@
runtime
.
children
).
toBe
(
@
children
)
...
...
common/static/coffee/src/xblock/core.coffee
View file @
6d67d0c6
@
XBlock
=
r
untime
:
{}
R
untime
:
{}
initializeBlock
:
(
element
)
->
$element
=
$
(
element
)
children
=
@
initializeBlocks
(
$element
)
runtime
=
$element
.
data
(
"runtime-class"
)
version
=
$element
.
data
(
"runtime-version"
)
initFnName
=
$element
.
data
(
"init"
)
if
version
?
and
initFnName
?
runtime
=
@
runtime
[
"v
#{
version
}
"
](
element
,
children
)
if
runtime
?
and
version
?
and
initFnName
?
runtime
=
new
window
[
runtime
]
[
"v
#{
version
}
"
](
element
,
children
)
initFn
=
window
[
initFnName
]
block
=
initFn
(
runtime
,
element
)
?
{}
else
elementTag
=
$
(
'<div>'
).
append
(
$element
.
clone
()).
html
();
console
.
log
(
"Block
#{
elementTag
}
is missing data-runtime-version or data-init, and can't be initialized"
)
console
.
log
(
"Block
#{
elementTag
}
is missing data-runtime
, data-runtime
-version or data-init, and can't be initialized"
)
block
=
{}
block
.
element
=
element
...
...
common/static/coffee/src/xblock/runtime.v1.coffee
View file @
6d67d0c6
@
XBlock
.
runtime
.
v1
=
(
element
,
children
)
->
childMap
=
{}
$
.
each
children
,
(
idx
,
child
)
->
childMap
[
child
.
name
]
=
child
return
{
# Generate the handler url for the specified handler.
#
# element is the html element containing the xblock requesting the url
# handlerName is the name of the handler
# suffix is the optional url suffix to include in the handler url
# query is an optional query-string (note, this should not include a preceding ? or &)
handlerUrl
:
(
element
,
handlerName
,
suffix
,
query
)
->
handlerPrefix
=
$
(
element
).
data
(
"handler-prefix"
)
suffix
=
if
suffix
?
then
"/
#{
suffix
}
"
else
''
query
=
if
query
?
then
"?
#{
query
}
"
else
''
"
#{
handlerPrefix
}
/
#{
handlerName
}#{
suffix
}#{
query
}
"
# A list of xblock children of this element
children
:
children
# A map of name -> child for the xblock children of this element
childMap
:
childMap
}
class
XBlock
.
Runtime
.
v1
constructor
:
(
@
element
,
@
children
)
->
@
childMap
=
{}
$
.
each
@
children
,
(
idx
,
child
)
=>
@
childMap
[
child
.
name
]
=
child
lms/djangoapps/courseware/module_render.py
View file @
6d67d0c6
...
...
@@ -21,7 +21,7 @@ from courseware.access import has_access, get_user_role
from
courseware.masquerade
import
setup_masquerade
from
courseware.model_data
import
FieldDataCache
,
DjangoKeyValueStore
from
lms.lib.xblock.field_data
import
LmsFieldData
from
lms.lib.xblock.runtime
import
LmsModuleSystem
,
handler_prefix
,
unquote_slashes
from
lms.lib.xblock.runtime
import
LmsModuleSystem
,
unquote_slashes
from
edxmako.shortcuts
import
render_to_string
from
psychometrics.psychoanalyze
import
make_psychometrics_data_update_handler
from
student.models
import
anonymous_id_for_user
,
user_by_anonymous_id
...
...
@@ -339,7 +339,7 @@ def get_module_for_descriptor_internal(user, descriptor, field_data_cache, cours
# Wrap the output display in a single div to allow for the XModule
# javascript to be bound correctly
if
wrap_xmodule_display
is
True
:
block_wrappers
.
append
(
partial
(
wrap_xblock
,
partial
(
handler_prefix
,
course_id
)
))
block_wrappers
.
append
(
partial
(
wrap_xblock
,
'LmsRuntime'
,
extra_data
=
{
'course-id'
:
course_id
}
))
# TODO (cpennington): When modules are shared between courses, the static
# prefix is going to have to be specific to the module, not the directory
...
...
@@ -379,7 +379,8 @@ def get_module_for_descriptor_internal(user, descriptor, field_data_cache, cours
# As we have the time to manually test more modules, we can add to the list
# of modules that get the per-course anonymized id.
is_pure_xblock
=
isinstance
(
descriptor
,
XBlock
)
and
not
isinstance
(
descriptor
,
XModuleDescriptor
)
is_lti_module
=
not
is_pure_xblock
and
issubclass
(
descriptor
.
module_class
,
LTIModule
)
module_class
=
getattr
(
descriptor
,
'module_class'
,
None
)
is_lti_module
=
not
is_pure_xblock
and
issubclass
(
module_class
,
LTIModule
)
if
is_pure_xblock
or
is_lti_module
:
anonymous_student_id
=
anonymous_id_for_user
(
user
,
course_id
)
else
:
...
...
lms/djangoapps/instructor/views/instructor_dashboard.py
View file @
6d67d0c6
...
...
@@ -24,7 +24,6 @@ from django_comment_client.utils import has_forum_access
from
django_comment_common.models
import
FORUM_ROLE_ADMINISTRATOR
from
student.models
import
CourseEnrollment
from
bulk_email.models
import
CourseAuthorization
from
lms.lib.xblock.runtime
import
handler_prefix
from
.tools
import
get_units_with_due_date
,
title_or_url
...
...
@@ -206,7 +205,7 @@ def _section_send_email(course_id, access, course):
ScopeIds
(
None
,
None
,
None
,
'i4x://dummy_org/dummy_course/html/dummy_name'
)
)
fragment
=
course
.
system
.
render
(
html_module
,
'studio_view'
)
fragment
=
wrap_xblock
(
partial
(
handler_prefix
,
course_id
),
html_module
,
'studio_view'
,
fragment
,
None
)
fragment
=
wrap_xblock
(
'LmsRuntime'
,
html_module
,
'studio_view'
,
fragment
,
None
,
extra_data
=
{
"course-id"
:
course_id
}
)
email_editor
=
fragment
.
content
section_data
=
{
'section_key'
:
'send_email'
,
...
...
lms/djangoapps/instructor/views/legacy.py
View file @
6d67d0c6
...
...
@@ -61,7 +61,6 @@ import track.views
from
xblock.field_data
import
DictFieldData
from
xblock.fields
import
ScopeIds
from
django.utils.translation
import
ugettext
as
_u
from
lms.lib.xblock.runtime
import
handler_prefix
from
microsite_configuration.middleware
import
MicrositeConfiguration
...
...
@@ -848,7 +847,7 @@ def instructor_dashboard(request, course_id):
ScopeIds
(
None
,
None
,
None
,
'i4x://dummy_org/dummy_course/html/dummy_name'
)
)
fragment
=
html_module
.
render
(
'studio_view'
)
fragment
=
wrap_xblock
(
partial
(
handler_prefix
,
course_id
),
html_module
,
'studio_view'
,
fragment
,
None
)
fragment
=
wrap_xblock
(
'LmsRuntime'
,
html_module
,
'studio_view'
,
fragment
,
None
,
extra_data
=
{
"course-id"
:
course_id
}
)
email_editor
=
fragment
.
content
# Enable instructor email only if the following conditions are met:
...
...
lms/envs/common.py
View file @
6d67d0c6
...
...
@@ -750,6 +750,7 @@ main_vendor_js = [
'js/vendor/ova/ova.js'
,
'js/vendor/ova/catch/js/catch.js'
,
'js/vendor/ova/catch/js/handlebars-1.1.2.js'
'js/vendor/URI.min.js'
]
discussion_js
=
sorted
(
rooted_glob
(
COMMON_ROOT
/
'static'
,
'coffee/src/discussion/**/*.js'
))
...
...
@@ -815,17 +816,18 @@ PIPELINE_CSS = {
}
common_js
=
set
(
rooted_glob
(
COMMON_ROOT
/
'static'
,
'coffee/src/**/*.js'
))
-
set
(
courseware_js
+
discussion_js
+
staff_grading_js
+
open_ended_js
+
notes_js
+
instructor_dash_js
)
project_js
=
set
(
rooted_glob
(
PROJECT_ROOT
/
'static'
,
'coffee/src/**/*.js'
))
-
set
(
courseware_js
+
discussion_js
+
staff_grading_js
+
open_ended_js
+
notes_js
+
instructor_dash_js
)
# test_order: Determines the position of this chunk of javascript on
# the jasmine test page
PIPELINE_JS
=
{
'application'
:
{
# Application will contain all paths not in courseware_only_js
'source_filenames'
:
sorted
(
set
(
rooted_glob
(
COMMON_ROOT
/
'static'
,
'coffee/src/**/*.js'
)
+
rooted_glob
(
PROJECT_ROOT
/
'static'
,
'coffee/src/**/*.js'
))
-
set
(
courseware_js
+
discussion_js
+
staff_grading_js
+
open_ended_js
+
notes_js
+
instructor_dash_js
)
)
+
[
'source_filenames'
:
sorted
(
common_js
)
+
sorted
(
project_js
)
+
[
'js/form.ext.js'
,
'js/my_courses_dropdown.js'
,
'js/toggle_login_modal.js'
,
...
...
lms/lib/xblock/runtime.py
View file @
6d67d0c6
...
...
@@ -58,63 +58,6 @@ def unquote_slashes(text):
return
re
.
sub
(
r'(;;|;_)'
,
_unquote_slashes
,
text
)
def
handler_url
(
course_id
,
block
,
handler
,
suffix
=
''
,
query
=
''
,
thirdparty
=
False
):
"""
Return an XBlock handler url for the specified course, block and handler.
If handler is an empty string, this function is being used to create a
prefix of the general URL, which is assumed to be followed by handler name
and suffix.
If handler is specified, then it is checked for being a valid handler
function, and ValueError is raised if not.
"""
view_name
=
'xblock_handler'
if
handler
:
# Be sure this is really a handler.
func
=
getattr
(
block
,
handler
,
None
)
if
not
func
:
raise
ValueError
(
"{!r} is not a function name"
.
format
(
handler
))
if
not
getattr
(
func
,
"_is_xblock_handler"
,
False
):
raise
ValueError
(
"{!r} is not a handler name"
.
format
(
handler
))
if
thirdparty
:
view_name
=
'xblock_handler_noauth'
url
=
reverse
(
view_name
,
kwargs
=
{
'course_id'
:
course_id
,
'usage_id'
:
quote_slashes
(
unicode
(
block
.
scope_ids
.
usage_id
)
.
encode
(
'utf-8'
)),
'handler'
:
handler
,
'suffix'
:
suffix
,
})
# If suffix is an empty string, remove the trailing '/'
if
not
suffix
:
url
=
url
.
rstrip
(
'/'
)
# If there is a query string, append it
if
query
:
url
+=
'?'
+
query
return
url
def
handler_prefix
(
course_id
,
block
):
"""
Returns a prefix for use by the Javascript handler_url function.
The prefix is a valid handler url after the handler name is slash-appended
to it.
"""
# This depends on handler url having the handler_name as the final piece of the url
# so that leaving an empty handler_name really does leave the opportunity to append
# the handler_name on the frontend
# This is relied on by the xblock/runtime.v1.coffee frontend handlerUrl function
return
handler_url
(
course_id
,
block
,
''
)
.
rstrip
(
'/?'
)
class
LmsHandlerUrls
(
object
):
"""
A runtime mixin that provides a handler_url function that routes
...
...
@@ -127,7 +70,34 @@ class LmsHandlerUrls(object):
# pylint: disable=no-member
def
handler_url
(
self
,
block
,
handler_name
,
suffix
=
''
,
query
=
''
,
thirdparty
=
False
):
"""See :method:`xblock.runtime:Runtime.handler_url`"""
return
handler_url
(
self
.
course_id
,
block
,
handler_name
,
suffix
=
''
,
query
=
''
,
thirdparty
=
thirdparty
)
view_name
=
'xblock_handler'
if
handler_name
:
# Be sure this is really a handler.
func
=
getattr
(
block
,
handler_name
,
None
)
if
not
func
:
raise
ValueError
(
"{!r} is not a function name"
.
format
(
handler_name
))
if
not
getattr
(
func
,
"_is_xblock_handler"
,
False
):
raise
ValueError
(
"{!r} is not a handler name"
.
format
(
handler_name
))
if
thirdparty
:
view_name
=
'xblock_handler_noauth'
url
=
reverse
(
view_name
,
kwargs
=
{
'course_id'
:
self
.
course_id
,
'usage_id'
:
quote_slashes
(
unicode
(
block
.
scope_ids
.
usage_id
)
.
encode
(
'utf-8'
)),
'handler'
:
handler_name
,
'suffix'
:
suffix
,
})
# If suffix is an empty string, remove the trailing '/'
if
not
suffix
:
url
=
url
.
rstrip
(
'/'
)
# If there is a query string, append it
if
query
:
url
+=
'?'
+
query
return
url
class
LmsModuleSystem
(
LmsHandlerUrls
,
ModuleSystem
):
# pylint: disable=abstract-method
...
...
lms/lib/xblock/test/test_runtime.py
View file @
6d67d0c6
...
...
@@ -6,7 +6,7 @@ from ddt import ddt, data
from
mock
import
Mock
from
unittest
import
TestCase
from
urlparse
import
urlparse
from
lms.lib.xblock.runtime
import
quote_slashes
,
unquote_slashes
,
handler_url
from
lms.lib.xblock.runtime
import
quote_slashes
,
unquote_slashes
,
LmsModuleSystem
TEST_STRINGS
=
[
''
,
...
...
@@ -41,23 +41,31 @@ class TestHandlerUrl(TestCase):
def
setUp
(
self
):
self
.
block
=
Mock
()
self
.
course_id
=
"org/course/run"
self
.
runtime
=
LmsModuleSystem
(
static_url
=
'/static'
,
track_function
=
Mock
(),
get_module
=
Mock
(),
render_template
=
Mock
(),
replace_urls
=
str
,
course_id
=
self
.
course_id
,
)
def
test_trailing_characters
(
self
):
self
.
assertFalse
(
handler_url
(
self
.
course_id
,
self
.
block
,
'handler'
)
.
endswith
(
'?'
))
self
.
assertFalse
(
handler_url
(
self
.
course_id
,
self
.
block
,
'handler'
)
.
endswith
(
'/'
))
self
.
assertFalse
(
self
.
runtime
.
handler_url
(
self
.
block
,
'handler'
)
.
endswith
(
'?'
))
self
.
assertFalse
(
self
.
runtime
.
handler_url
(
self
.
block
,
'handler'
)
.
endswith
(
'/'
))
self
.
assertFalse
(
handler_url
(
self
.
course_id
,
self
.
block
,
'handler'
,
'suffix'
)
.
endswith
(
'?'
))
self
.
assertFalse
(
handler_url
(
self
.
course_id
,
self
.
block
,
'handler'
,
'suffix'
)
.
endswith
(
'/'
))
self
.
assertFalse
(
self
.
runtime
.
handler_url
(
self
.
block
,
'handler'
,
'suffix'
)
.
endswith
(
'?'
))
self
.
assertFalse
(
self
.
runtime
.
handler_url
(
self
.
block
,
'handler'
,
'suffix'
)
.
endswith
(
'/'
))
self
.
assertFalse
(
handler_url
(
self
.
course_id
,
self
.
block
,
'handler'
,
'suffix'
,
'query'
)
.
endswith
(
'?'
))
self
.
assertFalse
(
handler_url
(
self
.
course_id
,
self
.
block
,
'handler'
,
'suffix'
,
'query'
)
.
endswith
(
'/'
))
self
.
assertFalse
(
self
.
runtime
.
handler_url
(
self
.
block
,
'handler'
,
'suffix'
,
'query'
)
.
endswith
(
'?'
))
self
.
assertFalse
(
self
.
runtime
.
handler_url
(
self
.
block
,
'handler'
,
'suffix'
,
'query'
)
.
endswith
(
'/'
))
self
.
assertFalse
(
handler_url
(
self
.
course_id
,
self
.
block
,
'handler'
,
query
=
'query'
)
.
endswith
(
'?'
))
self
.
assertFalse
(
handler_url
(
self
.
course_id
,
self
.
block
,
'handler'
,
query
=
'query'
)
.
endswith
(
'/'
))
self
.
assertFalse
(
self
.
runtime
.
handler_url
(
self
.
block
,
'handler'
,
query
=
'query'
)
.
endswith
(
'?'
))
self
.
assertFalse
(
self
.
runtime
.
handler_url
(
self
.
block
,
'handler'
,
query
=
'query'
)
.
endswith
(
'/'
))
def
_parsed_query
(
self
,
query_string
):
"""Return the parsed query string from a handler_url generated with the supplied query_string"""
return
urlparse
(
handler_url
(
self
.
course_id
,
self
.
block
,
'handler'
,
query
=
query_string
))
.
query
return
urlparse
(
self
.
runtime
.
handler_url
(
self
.
block
,
'handler'
,
query
=
query_string
))
.
query
def
test_query_string
(
self
):
self
.
assertIn
(
'foo=bar'
,
self
.
_parsed_query
(
'foo=bar'
))
...
...
@@ -66,7 +74,7 @@ class TestHandlerUrl(TestCase):
def
_parsed_path
(
self
,
handler_name
=
'handler'
,
suffix
=
''
):
"""Return the parsed path from a handler_url with the supplied handler_name and suffix"""
return
urlparse
(
handler_url
(
self
.
course_id
,
self
.
block
,
handler_name
,
suffix
=
suffix
))
.
path
return
urlparse
(
self
.
runtime
.
handler_url
(
self
.
block
,
handler_name
,
suffix
=
suffix
))
.
path
def
test_suffix
(
self
):
self
.
assertTrue
(
self
.
_parsed_path
(
suffix
=
"foo"
)
.
endswith
(
'foo'
))
...
...
lms/static/coffee/src/xblock/lms.runtime.v1.coffee
0 → 100644
View file @
6d67d0c6
@
LmsRuntime
=
{}
class
LmsRuntime
.
v1
extends
XBlock
.
Runtime
.
v1
handlerUrl
:
(
element
,
handlerName
,
suffix
,
query
,
thirdparty
)
->
courseId
=
$
(
@
element
).
data
(
"course-id"
)
usageId
=
$
(
@
element
).
data
(
"usage-id"
)
handlerAuth
=
if
thirdparty
then
"handler_noauth"
else
"handler"
uri
=
URI
(
'/courses'
).
segment
(
courseId
)
.
segment
(
'xblock'
)
.
segment
(
usageId
)
.
segment
(
handlerAuth
)
.
segment
(
handlerName
)
if
suffix
?
then
uri
.
segment
(
suffix
)
if
query
?
then
uri
.
search
(
query
)
uri
.
toString
()
lms/static/js_test.yml
View file @
6d67d0c6
...
...
@@ -40,6 +40,7 @@ lib_paths:
-
xmodule_js/common_static/js/vendor/jquery.cookie.js
-
xmodule_js/common_static/js/vendor/flot/jquery.flot.js
-
xmodule_js/common_static/js/vendor/CodeMirror/codemirror.js
-
xmodule_js/common_static/js/vendor/URI.min.js
-
xmodule_js/common_static/coffee/src/jquery.immediateDescendents.js
-
xmodule_js/common_static/coffee/src/xblock
-
xmodule_js/src/capa/
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment