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
d861a9a0
Commit
d861a9a0
authored
Apr 22, 2014
by
Anton Stupak
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #3369 from edx/anton/download-handout
Video: Add Download Handout
parents
ac980460
127bf7ed
Hide whitespace changes
Inline
Side-by-side
Showing
24 changed files
with
498 additions
and
81 deletions
+498
-81
CHANGELOG.rst
+3
-0
cms/djangoapps/contentstore/features/video.py
+1
-0
cms/djangoapps/contentstore/features/video_editor.feature
+0
-0
cms/djangoapps/contentstore/features/video_editor.py
+1
-0
cms/djangoapps/contentstore/features/video_handout.feature
+71
-0
cms/djangoapps/contentstore/features/video_handout.py
+40
-0
cms/static/coffee/spec/main_squire.coffee
+2
-1
cms/static/coffee/spec/models/upload_spec.coffee
+4
-4
cms/static/js/models/uploads.js
+2
-1
cms/static/js/spec/video/file_uploader_editor_spec.js
+176
-0
cms/static/js/spec/video/translations_editor_spec.js
+19
-51
cms/static/js/views/abstract_editor.js
+15
-6
cms/static/js/views/metadata.js
+65
-5
cms/static/sass/views/_unit.scss
+35
-0
cms/templates/js/metadata-file-uploader-entry.underscore
+9
-0
cms/templates/js/metadata-file-uploader-item.underscore
+3
-0
cms/templates/widgets/metadata-edit.html
+1
-1
cms/templates/widgets/tabs/metadata-edit-tab.html
+1
-1
common/lib/xmodule/xmodule/css/video/display.scss
+1
-2
common/lib/xmodule/xmodule/tests/test_video.py
+13
-2
common/lib/xmodule/xmodule/video_module/video_module.py
+12
-0
common/lib/xmodule/xmodule/video_module/video_xfields.py
+12
-5
lms/djangoapps/courseware/tests/test_video_mongo.py
+5
-0
lms/templates/video.html
+7
-2
No files found.
CHANGELOG.rst
View file @
d861a9a0
...
@@ -5,6 +5,9 @@ These are notable changes in edx-platform. This is a rolling list of changes,
...
@@ -5,6 +5,9 @@ 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
in roughly chronological order, most recent first. Add your entries at or near
the top. Include a label indicating the component affected.
the top. Include a label indicating the component affected.
Blades: Add an upload button for authors to provide students with an option to
download a handout associated with a video (of arbitrary file format). BLD-1000.
Blades: Show the HD button only if there is an HD version available. BLD-937.
Blades: Show the HD button only if there is an HD version available. BLD-937.
Studio: Add edit button to leaf xblocks on the container page. STUD-1306.
Studio: Add edit button to leaf xblocks on the container page. STUD-1306.
...
...
cms/djangoapps/contentstore/features/video.py
View file @
d861a9a0
...
@@ -11,6 +11,7 @@ VIDEO_BUTTONS = {
...
@@ -11,6 +11,7 @@ VIDEO_BUTTONS = {
'volume'
:
'.volume'
,
'volume'
:
'.volume'
,
'play'
:
'.video_control.play'
,
'play'
:
'.video_control.play'
,
'pause'
:
'.video_control.pause'
,
'pause'
:
'.video_control.pause'
,
'handout'
:
'.video-handout.video-download-button a'
,
}
}
SELECTORS
=
{
SELECTORS
=
{
...
...
cms/djangoapps/contentstore/features/video
-
editor.feature
→
cms/djangoapps/contentstore/features/video
_
editor.feature
View file @
d861a9a0
File moved
cms/djangoapps/contentstore/features/video
-
editor.py
→
cms/djangoapps/contentstore/features/video
_
editor.py
View file @
d861a9a0
...
@@ -148,6 +148,7 @@ def correct_video_settings(_step):
...
@@ -148,6 +148,7 @@ def correct_video_settings(_step):
[
'Transcript Display'
,
'True'
,
False
],
[
'Transcript Display'
,
'True'
,
False
],
[
'Transcript Download Allowed'
,
'False'
,
False
],
[
'Transcript Download Allowed'
,
'False'
,
False
],
[
'Transcript Translations'
,
''
,
False
],
[
'Transcript Translations'
,
''
,
False
],
[
'Upload Handout'
,
''
,
False
],
[
'Video Download Allowed'
,
'False'
,
False
],
[
'Video Download Allowed'
,
'False'
,
False
],
[
'Video Sources'
,
''
,
False
],
[
'Video Sources'
,
''
,
False
],
[
'Youtube ID'
,
'OEoXaMPEzfM'
,
False
],
[
'Youtube ID'
,
'OEoXaMPEzfM'
,
False
],
...
...
cms/djangoapps/contentstore/features/video_handout.feature
0 → 100644
View file @
d861a9a0
@shard_3
Feature
:
CMS Video Component Handout
As a course author, I want to be able to create video handout
# 1
Scenario
:
Handout uploading works correctly
Given
I have created a Video component with handout file
"textbook.pdf"
And
I save changes
Then
I can see video button
"handout"
And
I can download handout file with mime type
"application/pdf"
# 2
Scenario
:
Handout downloading works correctly w/ preliminary saving
Given
I have created a Video component with handout file
"textbook.pdf"
And
I save changes
And
I edit the component
And
I open tab
"Advanced"
And
I can download handout file in editor with mime type
"application/pdf"
# 3
Scenario
:
Handout downloading works correctly w/o preliminary saving
Given
I have created a Video component with handout file
"textbook.pdf"
And
I can download handout file in editor with mime type
"application/pdf"
# 4
Scenario
:
Handout clearing works correctly w/ preliminary saving
Given
I have created a Video component with handout file
"textbook.pdf"
And
I save changes
And
I can download handout file with mime type
"application/pdf"
And
I edit the component
And
I open tab
"Advanced"
And
I clear handout
And
I save changes
Then
I do not see video button
"handout"
# 5
Scenario
:
Handout clearing works correctly w/o preliminary saving
Given
I have created a Video component with handout file
"asset.html"
And
I clear handout
And
I save changes
Then
I do not see video button
"handout"
# 6
Scenario
:
User can easy replace the handout by another one w/ preliminary saving
Given
I have created a Video component with handout file
"asset.html"
And
I save changes
Then
I can see video button
"handout"
And
I can download handout file with mime type
"text/html"
And
I edit the component
And
I open tab
"Advanced"
And
I replace handout file by
"textbook.pdf"
And
I save changes
Then
I can see video button
"handout"
And
I can download handout file with mime type
"application/pdf"
# 7
Scenario
:
User can easy replace the handout by another one w/o preliminary saving
Given
I have created a Video component with handout file
"asset.html"
And
I replace handout file by
"textbook.pdf"
And
I save changes
Then
I can see video button
"handout"
And
I can download handout file with mime type
"application/pdf"
# 8
Scenario
:
Upload file "A" -> Remove it -> Upload file "B"
Given
I have created a Video component with handout file
"asset.html"
And
I clear handout
And
I upload handout file
"textbook.pdf"
And
I save changes
Then
I can see video button
"handout"
And
I can download handout file with mime type
"application/pdf"
cms/djangoapps/contentstore/features/video_handout.py
0 → 100644
View file @
d861a9a0
# -*- coding: utf-8 -*-
# disable missing docstring
# pylint: disable=C0111
from
lettuce
import
world
,
step
from
nose.tools
import
assert_true
# pylint: disable=E0611
from
video_editor
import
RequestHandlerWithSessionId
,
success_upload_file
@step
(
'I (?:upload|replace) handout file(?: by)? "([^"]*)"$'
)
def
upload_handout
(
step
,
filename
):
world
.
css_click
(
'.wrapper-comp-setting.file-uploader .upload-action'
)
success_upload_file
(
filename
)
@step
(
'I can download handout file( in editor)? with mime type "([^"]*)"$'
)
def
i_can_download_handout_with_mime_type
(
_step
,
is_editor
,
mime_type
):
if
is_editor
:
selector
=
'.wrapper-comp-setting.file-uploader .download-action'
else
:
selector
=
'.video-handout.video-download-button a'
button
=
world
.
css_find
(
selector
)
.
first
url
=
button
[
'href'
]
request
=
RequestHandlerWithSessionId
()
assert_true
(
request
.
get
(
url
)
.
is_success
())
assert_true
(
request
.
check_header
(
'content-type'
,
mime_type
))
@step
(
'I clear handout$'
)
def
clear_handout
(
_step
):
world
.
css_click
(
'.wrapper-comp-setting.file-uploader .setting-clear'
)
@step
(
'I have created a Video component with handout file "([^"]*)"'
)
def
create_video_with_handout
(
_step
,
filename
):
_step
.
given
(
'I have created a Video component'
)
_step
.
given
(
'I edit the component'
)
_step
.
given
(
'I open tab "Advanced"'
)
_step
.
given
(
'I upload handout file "{0}"'
.
format
(
filename
))
cms/static/coffee/spec/main_squire.coffee
View file @
d861a9a0
...
@@ -175,5 +175,6 @@ jasmine.getFixtures().fixturesPath += 'coffee/fixtures'
...
@@ -175,5 +175,6 @@ jasmine.getFixtures().fixturesPath += 'coffee/fixtures'
define
([
define
([
"coffee/spec/views/assets_spec"
,
"coffee/spec/views/assets_spec"
,
"js/spec/video/translations_editor_spec"
"js/spec/video/translations_editor_spec"
,
"js/spec/video/file_uploader_editor_spec"
])
])
cms/static/coffee/spec/models/upload_spec.coffee
View file @
d861a9a0
...
@@ -13,15 +13,15 @@ define ["js/models/uploads"], (FileUpload) ->
...
@@ -13,15 +13,15 @@ define ["js/models/uploads"], (FileUpload) ->
it
"is valid by default"
,
->
it
"is valid by default"
,
->
expect
(
@
model
.
isValid
()).
toBeTruthy
()
expect
(
@
model
.
isValid
()).
toBeTruthy
()
it
"is
in
valid for text files by default"
,
->
it
"is valid for text files by default"
,
->
file
=
{
"type"
:
"text/plain"
,
"name"
:
"filename.txt"
}
file
=
{
"type"
:
"text/plain"
,
"name"
:
"filename.txt"
}
@
model
.
set
(
"selectedFile"
,
file
);
@
model
.
set
(
"selectedFile"
,
file
);
expect
(
@
model
.
isValid
()).
toBe
Fals
y
()
expect
(
@
model
.
isValid
()).
toBe
Truth
y
()
it
"is
in
valid for PNG files by default"
,
->
it
"is valid for PNG files by default"
,
->
file
=
{
"type"
:
"image/png"
,
"name"
:
"filename.png"
}
file
=
{
"type"
:
"image/png"
,
"name"
:
"filename.png"
}
@
model
.
set
(
"selectedFile"
,
file
);
@
model
.
set
(
"selectedFile"
,
file
);
expect
(
@
model
.
isValid
()).
toBe
Fals
y
()
expect
(
@
model
.
isValid
()).
toBe
Truth
y
()
it
"can accept a file type when explicitly set"
,
->
it
"can accept a file type when explicitly set"
,
->
file
=
{
"type"
:
"image/png"
,
"name"
:
"filename.png"
}
file
=
{
"type"
:
"image/png"
,
"name"
:
"filename.png"
}
...
...
cms/static/js/models/uploads.js
View file @
d861a9a0
...
@@ -47,7 +47,8 @@ var FileUpload = Backbone.Model.extend({
...
@@ -47,7 +47,8 @@ var FileUpload = Backbone.Model.extend({
return
RegExp
((
'(?:.+)
\\
.('
+
formats
.
join
(
'|'
)
+
')$'
),
'i'
);
return
RegExp
((
'(?:.+)
\\
.('
+
formats
.
join
(
'|'
)
+
')$'
),
'i'
);
};
};
return
_
.
contains
(
attrs
.
mimeTypes
,
file
.
type
)
||
return
(
attrs
.
mimeTypes
.
length
===
0
&&
attrs
.
fileFormats
.
length
===
0
)
||
_
.
contains
(
attrs
.
mimeTypes
,
file
.
type
)
||
getRegExp
(
attrs
.
fileFormats
).
test
(
file
.
name
);
getRegExp
(
attrs
.
fileFormats
).
test
(
file
.
name
);
},
},
// Return strings for the valid file types and extensions this
// Return strings for the valid file types and extensions this
...
...
cms/static/js/spec/video/file_uploader_editor_spec.js
0 → 100644
View file @
d861a9a0
define
(
[
'jquery'
,
'underscore'
,
'js/spec_helpers/create_sinon'
,
'squire'
],
function
(
$
,
_
,
create_sinon
,
Squire
)
{
'use strict'
;
describe
(
'FileUploader'
,
function
()
{
var
FileUploaderTemplate
=
readFixtures
(
'metadata-file-uploader-entry.underscore'
),
FileUploaderItemTemplate
=
readFixtures
(
'metadata-file-uploader-item.underscore'
),
locator
=
'locator'
,
feedbackTpl
=
readFixtures
(
'system-feedback.underscore'
),
modelStub
=
{
default_value
:
'http://example.org/test_1'
,
display_name
:
'File Upload'
,
explicitly_set
:
false
,
field_name
:
'file_upload'
,
help
:
'Specifies the name for this component.'
,
type
:
'FileUploader'
,
value
:
'http://example.org/test_1'
},
self
,
injector
;
var
setValue
=
function
(
view
,
value
)
{
view
.
setValueInEditor
(
value
);
view
.
updateModel
();
};
var
createPromptSpy
=
function
(
name
)
{
var
spy
=
jasmine
.
createSpyObj
(
name
,
[
'constructor'
,
'show'
,
'hide'
]);
spy
.
constructor
.
andReturn
(
spy
);
spy
.
show
.
andReturn
(
spy
);
spy
.
extend
=
jasmine
.
createSpy
().
andReturn
(
spy
.
constructor
);
return
spy
;
};
beforeEach
(
function
()
{
self
=
this
;
this
.
addMatchers
({
assertValueInView
:
function
(
expected
)
{
var
value
=
this
.
actual
.
getValueFromEditor
();
return
this
.
env
.
equals_
(
value
,
expected
);
},
assertCanUpdateView
:
function
(
expected
)
{
var
view
=
this
.
actual
,
value
;
view
.
setValueInEditor
(
expected
);
value
=
view
.
getValueFromEditor
();
return
this
.
env
.
equals_
(
value
,
expected
);
},
assertClear
:
function
(
modelValue
)
{
var
env
=
this
.
env
,
view
=
this
.
actual
,
model
=
view
.
model
;
return
model
.
getValue
()
===
null
&&
env
.
equals_
(
model
.
getDisplayValue
(),
modelValue
)
&&
env
.
equals_
(
view
.
getValueFromEditor
(),
modelValue
);
},
assertUpdateModel
:
function
(
originalValue
,
newValue
)
{
var
env
=
this
.
env
,
view
=
this
.
actual
,
model
=
view
.
model
,
expectOriginal
;
view
.
setValueInEditor
(
newValue
);
expectOriginal
=
env
.
equals_
(
model
.
getValue
(),
originalValue
);
view
.
updateModel
();
return
expectOriginal
&&
env
.
equals_
(
model
.
getValue
(),
newValue
);
},
verifyButtons
:
function
(
upload
,
download
,
index
)
{
var
view
=
this
.
actual
,
uploadBtn
=
view
.
$
(
'.upload-setting'
),
downloadBtn
=
view
.
$
(
'.download-setting'
);
upload
=
upload
?
uploadBtn
.
length
:
!
uploadBtn
.
length
;
download
=
download
?
downloadBtn
.
length
:
!
downloadBtn
.
length
;
return
upload
&&
download
;
}
});
appendSetFixtures
(
$
(
'<script>'
,
{
id
:
'metadata-file-uploader-entry'
,
type
:
'text/template'
}).
text
(
FileUploaderTemplate
));
appendSetFixtures
(
$
(
'<script>'
,
{
id
:
'metadata-file-uploader-item'
,
type
:
'text/template'
}).
text
(
FileUploaderItemTemplate
));
this
.
uploadSpies
=
createPromptSpy
(
'UploadDialog'
);
injector
=
new
Squire
();
injector
.
mock
(
'js/views/uploads'
,
function
()
{
return
self
.
uploadSpies
.
constructor
;
});
injector
.
mock
(
'js/views/video/transcripts/metadata_videolist'
);
injector
.
mock
(
'js/views/video/translations_editor'
);
runs
(
function
()
{
injector
.
require
([
'js/models/metadata'
,
'js/views/metadata'
],
function
(
MetadataModel
,
MetadataView
)
{
var
model
=
new
MetadataModel
(
$
.
extend
(
true
,
{},
modelStub
));
self
.
view
=
new
MetadataView
.
FileUploader
({
model
:
model
,
locator
:
locator
});
});
});
waitsFor
(
function
()
{
return
self
.
view
;
},
'FileUploader was not created'
,
2000
);
});
afterEach
(
function
()
{
injector
.
clean
();
injector
.
remove
();
});
it
(
'returns the initial value upon initialization'
,
function
()
{
expect
(
this
.
view
).
assertValueInView
(
'http://example.org/test_1'
);
expect
(
this
.
view
).
verifyButtons
(
true
,
true
);
});
it
(
'updates its value correctly'
,
function
()
{
expect
(
this
.
view
).
assertCanUpdateView
(
'http://example.org/test_2'
);
});
it
(
'upload works correctly'
,
function
()
{
var
options
;
setValue
(
this
.
view
,
''
);
expect
(
this
.
view
).
verifyButtons
(
true
,
false
);
this
.
view
.
$el
.
find
(
'.upload-setting'
).
click
();
expect
(
this
.
uploadSpies
.
constructor
).
toHaveBeenCalled
();
expect
(
this
.
uploadSpies
.
show
).
toHaveBeenCalled
();
options
=
this
.
uploadSpies
.
constructor
.
mostRecentCall
.
args
[
0
];
options
.
onSuccess
({
'asset'
:
{
'url'
:
'http://example.org/test_3'
}
});
expect
(
this
.
view
).
verifyButtons
(
true
,
true
);
expect
(
this
.
view
.
getValueFromEditor
()).
toEqual
(
'http://example.org/test_3'
);
});
it
(
'has a clear method to revert to the model default'
,
function
()
{
setValue
(
this
.
view
,
'http://example.org/test_5'
);
this
.
view
.
clear
();
expect
(
this
.
view
).
assertClear
(
'http://example.org/test_1'
);
});
it
(
'has an update model method'
,
function
()
{
expect
(
this
.
view
).
assertUpdateModel
(
null
,
'http://example.org/test_6'
);
});
});
});
cms/static/js/spec/video/translations_editor_spec.js
View file @
d861a9a0
...
@@ -212,43 +212,22 @@ function ($, _, create_sinon, Squire) {
...
@@ -212,43 +212,22 @@ function ($, _, create_sinon, Squire) {
});
});
});
});
describe
(
'has a clear method to revert to the model default'
,
function
()
{
it
(
'has a clear method to revert to the model default'
,
function
()
{
it
(
'w/ popup, if values were changed'
,
function
(){
setValue
(
this
.
view
,
{
var
requests
=
create_sinon
.
requests
(
this
),
'fr'
:
'en.srt'
,
options
;
'uk'
:
'ru.srt'
setValue
(
this
.
view
,
{
'fr'
:
'fr.srt'
,
'uk'
:
'uk.srt'
});
this
.
view
.
$el
.
find
(
'.create-setting'
).
click
();
this
.
view
.
clear
();
expect
(
this
.
view
).
assertClear
({
'en'
:
'en.srt'
,
'ru'
:
'ru.srt'
});
expect
(
this
.
view
.
$el
.
find
(
'.create-setting'
)).
not
.
toHaveClass
(
'is-disabled'
);
});
});
it
(
'w/o popup, if just keys were changed'
,
function
(){
this
.
view
.
$el
.
find
(
'.create-setting'
).
click
();
setValue
(
this
.
view
,
{
'fr'
:
'en.srt'
,
'uk'
:
'ru.srt'
});
this
.
view
.
$el
.
find
(
'.create-setting'
).
click
();
this
.
view
.
clear
();
expect
(
this
.
view
).
assertClear
({
this
.
view
.
clear
();
'en'
:
'en.srt'
,
'ru'
:
'ru.srt'
});
expect
(
this
.
view
.
$el
.
find
(
'.create-setting'
)).
not
.
toHaveClass
(
'is-disabled'
);
expect
(
this
.
view
).
assertClear
({
'en'
:
'en.srt'
,
'ru'
:
'ru.srt'
});
});
expect
(
this
.
view
.
$el
.
find
(
'.create-setting'
)).
not
.
toHaveClass
(
'is-disabled'
);
});
});
it
(
'has an update model method'
,
function
()
{
it
(
'has an update model method'
,
function
()
{
...
@@ -261,26 +240,15 @@ function ($, _, create_sinon, Squire) {
...
@@ -261,26 +240,15 @@ function ($, _, create_sinon, Squire) {
expect
(
this
.
view
.
$el
.
find
(
'select'
).
length
).
toEqual
(
5
);
expect
(
this
.
view
.
$el
.
find
(
'select'
).
length
).
toEqual
(
5
);
});
});
describe
(
'can remove an entry'
,
function
()
{
it
(
'can remove an entry'
,
function
()
{
it
(
'w/ popup, if values were changed'
,
function
(){
setValue
(
this
.
view
,
{
var
requests
=
create_sinon
.
requests
(
this
),
'en'
:
'en.srt'
,
options
;
'ru'
:
'ru.srt'
,
'fr'
:
''
expect
(
_
.
keys
(
this
.
view
.
model
.
get
(
'value'
)).
length
).
toEqual
(
4
);
this
.
view
.
$el
.
find
(
'.remove-setting'
).
last
().
click
();
expect
(
_
.
keys
(
this
.
view
.
model
.
get
(
'value'
)).
length
).
toEqual
(
3
);
});
it
(
'w/o popup, if just keys were changed'
,
function
(){
setValue
(
this
.
view
,
{
'en'
:
'en.srt'
,
'ru'
:
'ru.srt'
,
'fr'
:
''
});
expect
(
_
.
keys
(
this
.
view
.
model
.
get
(
'value'
)).
length
).
toEqual
(
3
);
this
.
view
.
$el
.
find
(
'.remove-setting'
).
last
().
click
();
expect
(
_
.
keys
(
this
.
view
.
model
.
get
(
'value'
)).
length
).
toEqual
(
2
);
});
});
expect
(
_
.
keys
(
this
.
view
.
model
.
get
(
'value'
)).
length
).
toEqual
(
3
);
this
.
view
.
$el
.
find
(
'.remove-setting'
).
last
().
click
();
expect
(
_
.
keys
(
this
.
view
.
model
.
get
(
'value'
)).
length
).
toEqual
(
2
);
});
});
it
(
'only allows one blank entry at a time'
,
function
()
{
it
(
'only allows one blank entry at a time'
,
function
()
{
...
...
cms/static/js/views/abstract_editor.js
View file @
d861a9a0
...
@@ -8,12 +8,7 @@ define(["js/views/baseview", "underscore"], function(BaseView, _) {
...
@@ -8,12 +8,7 @@ define(["js/views/baseview", "underscore"], function(BaseView, _) {
var
templateName
=
_
.
result
(
this
,
'templateName'
);
var
templateName
=
_
.
result
(
this
,
'templateName'
);
// Backbone model cid is only unique within the collection.
// Backbone model cid is only unique within the collection.
this
.
uniqueId
=
_
.
uniqueId
(
templateName
+
"_"
);
this
.
uniqueId
=
_
.
uniqueId
(
templateName
+
"_"
);
this
.
template
=
this
.
loadTemplate
(
templateName
);
var
tpl
=
document
.
getElementById
(
templateName
).
text
;
if
(
!
tpl
)
{
console
.
error
(
"Couldn't load template: "
+
templateName
);
}
this
.
template
=
_
.
template
(
tpl
);
this
.
$el
.
html
(
this
.
template
({
model
:
this
.
model
,
uniqueId
:
this
.
uniqueId
}));
this
.
$el
.
html
(
this
.
template
({
model
:
this
.
model
,
uniqueId
:
this
.
uniqueId
}));
this
.
listenTo
(
this
.
model
,
'change'
,
this
.
render
);
this
.
listenTo
(
this
.
model
,
'change'
,
this
.
render
);
this
.
render
();
this
.
render
();
...
@@ -85,6 +80,20 @@ define(["js/views/baseview", "underscore"], function(BaseView, _) {
...
@@ -85,6 +80,20 @@ define(["js/views/baseview", "underscore"], function(BaseView, _) {
}
}
return
this
;
return
this
;
},
/**
* Loads the named template from the page, or logs an error if it fails.
* @param name The name of the template.
* @returns The loaded template.
*/
loadTemplate
:
function
(
name
)
{
var
templateSelector
=
"#"
+
name
,
templateText
=
$
(
templateSelector
).
text
();
if
(
!
templateText
)
{
console
.
error
(
"Failed to load "
+
name
+
" template"
);
}
return
_
.
template
(
templateText
);
}
}
});
});
...
...
cms/static/js/views/metadata.js
View file @
d861a9a0
define
(
define
(
[
[
"js/views/baseview"
,
"underscore"
,
"js/models/metadata"
,
"js/views/abstract_editor"
,
"js/views/baseview"
,
"underscore"
,
"js/models/metadata"
,
"js/views/abstract_editor"
,
"js/models/uploads"
,
"js/views/uploads"
,
"js/views/video/transcripts/metadata_videolist"
,
"js/views/video/transcripts/metadata_videolist"
,
"js/views/video/translations_editor"
"js/views/video/translations_editor"
],
],
function
(
BaseView
,
_
,
MetadataModel
,
AbstractEditor
,
VideoList
,
VideoTranslations
)
{
function
(
BaseView
,
_
,
MetadataModel
,
AbstractEditor
,
FileUpload
,
UploadDialog
,
VideoList
,
VideoTranslations
)
{
var
Metadata
=
{};
var
Metadata
=
{};
Metadata
.
Editor
=
BaseView
.
extend
({
Metadata
.
Editor
=
BaseView
.
extend
({
// Model is CMS.Models.MetadataCollection,
// Model is CMS.Models.MetadataCollection,
initialize
:
function
()
{
initialize
:
function
()
{
this
.
template
=
this
.
loadTemplate
(
'metadata-editor'
);
var
self
=
this
,
counter
=
0
,
locator
=
self
.
$el
.
closest
(
'[data-locator]'
).
data
(
'locator'
);
this
.
template
=
this
.
loadTemplate
(
'metadata-editor'
);
this
.
$el
.
html
(
this
.
template
({
numEntries
:
this
.
collection
.
length
}));
this
.
$el
.
html
(
this
.
template
({
numEntries
:
this
.
collection
.
length
}));
var
counter
=
0
;
var
self
=
this
;
this
.
collection
.
each
(
this
.
collection
.
each
(
function
(
model
)
{
function
(
model
)
{
var
data
=
{
var
data
=
{
el
:
self
.
$el
.
find
(
'.metadata_entry'
)[
counter
++
],
el
:
self
.
$el
.
find
(
'.metadata_entry'
)[
counter
++
],
locator
:
locator
,
model
:
model
model
:
model
},
},
conversions
=
{
conversions
=
{
...
@@ -85,7 +88,7 @@ function(BaseView, _, MetadataModel, AbstractEditor, VideoList, VideoTranslation
...
@@ -85,7 +88,7 @@ function(BaseView, _, MetadataModel, AbstractEditor, VideoList, VideoTranslation
events
:
{
events
:
{
"change input"
:
"updateModel"
,
"change input"
:
"updateModel"
,
"keypress .setting-input"
:
"showClearButton"
,
"keypress .setting-input"
:
"showClearButton"
,
"click .setting-clear"
:
"clear"
"click .setting-clear"
:
"clear"
},
},
...
@@ -487,5 +490,62 @@ function(BaseView, _, MetadataModel, AbstractEditor, VideoList, VideoTranslation
...
@@ -487,5 +490,62 @@ function(BaseView, _, MetadataModel, AbstractEditor, VideoList, VideoTranslation
}
}
});
});
/**
* Provides convenient way to upload/download files in component edit.
* The editor uploads files directly to course assets and stores link
* to uploaded file.
*/
Metadata
.
FileUploader
=
AbstractEditor
.
extend
({
events
:
{
"click .upload-setting"
:
"upload"
,
"click .setting-clear"
:
"clear"
},
templateName
:
"metadata-file-uploader-entry"
,
templateButtonsName
:
"metadata-file-uploader-item"
,
initialize
:
function
()
{
this
.
buttonTemplate
=
this
.
loadTemplate
(
this
.
templateButtonsName
);
AbstractEditor
.
prototype
.
initialize
.
apply
(
this
);
},
getValueFromEditor
:
function
()
{
return
this
.
$
(
'#'
+
this
.
uniqueId
).
val
();
},
setValueInEditor
:
function
(
value
)
{
var
html
=
this
.
buttonTemplate
({
model
:
this
.
model
,
uniqueId
:
this
.
uniqueId
});
this
.
$
(
'#'
+
this
.
uniqueId
).
val
(
value
);
this
.
$
(
'.wrapper-uploader-actions'
).
html
(
html
);
},
upload
:
function
(
event
)
{
var
self
=
this
,
target
=
$
(
event
.
currentTarget
),
url
=
/assets/
+
this
.
options
.
locator
,
model
=
new
FileUpload
({
title
:
gettext
(
'Upload File'
),
}),
view
=
new
UploadDialog
({
model
:
model
,
url
:
url
,
parentElement
:
target
.
closest
(
'.xblock-editor'
),
onSuccess
:
function
(
response
)
{
if
(
response
[
'asset'
]
&&
response
[
'asset'
][
'url'
])
{
self
.
model
.
setValue
(
response
[
'asset'
][
'url'
]);
}
}
}).
show
();
event
.
preventDefault
();
}
});
return
Metadata
;
return
Metadata
;
});
});
cms/static/sass/views/_unit.scss
View file @
d861a9a0
...
@@ -609,6 +609,41 @@ body.course.unit,.view-unit {
...
@@ -609,6 +609,41 @@ body.course.unit,.view-unit {
display
:
block
;
display
:
block
;
}
}
.file-uploader
{
.upload-setting
{
@extend
%ui-btn-flat-outline
;
@extend
%t-action3
;
@include
box-sizing
(
border-box
);
display
:
inline-block
;
padding
:
(
$baseline
/
2
);
font-weight
:
600
;
width
:
49%
;
margin-right
:
2%
;
}
.download-setting
{
@extend
%ui-btn-non
;
@extend
%t-action4
;
@include
box-sizing
(
border-box
);
display
:
inline-block
;
padding
:
(
$baseline
/
2
);
font-weight
:
600
;
width
:
49%
;
text-align
:
center
;
color
:
$blue
;
&
:hover
{
background-color
:
$blue
;
}
}
.wrapper-uploader-actions
{
width
:
45%
;
display
:
inline-block
;
min-width
:
(
$baseline
*
5
);
}
}
//settings-list
//settings-list
.list-input.settings-list
{
.list-input.settings-list
{
margin
:
0
;
margin
:
0
;
...
...
cms/templates/js/metadata-file-uploader-entry.underscore
0 → 100644
View file @
d861a9a0
<div class="wrapper-comp-setting file-uploader">
<label class="label setting-label"><%= model.get('display_name') %></label>
<input type="hidden" id="<%= uniqueId %>" class="input setting-input" value="<%= model.get("value") %>">
<div class="wrapper-uploader-actions"></div>
<button class="action setting-clear inactive" type="button" name="setting-clear" value="<%= gettext("Clear") %>" data-tooltip="<%= gettext("Clear") %>">
<i class="icon-undo"></i><span class="sr">"<%= gettext("Clear Value") %>"</span>
</button>
</div>
<span class="tip setting-help"><%= model.get('help') %></span>
cms/templates/js/metadata-file-uploader-item.underscore
0 → 100644
View file @
d861a9a0
<a href="#" class="upload-action upload-setting"><%= model.get('value') ? gettext('Replace') : gettext('Upload') %>
</a><% if (model.get('value')) { %><a href="<%= model.get("value") %>" target="_blank" class="download-action download-setting"><%= gettext("Download") %>
</a><% } %>
cms/templates/widgets/metadata-edit.html
View file @
d861a9a0
...
@@ -12,7 +12,7 @@
...
@@ -12,7 +12,7 @@
<script
id=
"metadata-editor-tpl"
type=
"text/template"
>
<script
id=
"metadata-editor-tpl"
type=
"text/template"
>
<%
static
:
include
path
=
"js/metadata-editor.underscore"
/>
<%
static
:
include
path
=
"js/metadata-editor.underscore"
/>
</script>
</script>
% for template_name in ["metadata-number-entry", "metadata-string-entry", "metadata-option-entry", "metadata-list-entry", "metadata-dict-entry"]:
% for template_name in ["metadata-number-entry", "metadata-string-entry", "metadata-option-entry", "metadata-list-entry", "metadata-dict-entry"
, "metadata-file-uploader-entry", "metadata-file-uploader-item"
]:
<script
id=
"${template_name}"
type=
"text/template"
>
<script
id=
"${template_name}"
type=
"text/template"
>
<%
static
:
include
path
=
"js/${template_name}.underscore"
/>
<%
static
:
include
path
=
"js/${template_name}.underscore"
/>
</script>
</script>
...
...
cms/templates/widgets/tabs/metadata-edit-tab.html
View file @
d861a9a0
...
@@ -8,7 +8,7 @@
...
@@ -8,7 +8,7 @@
<%
static
:
include
path
=
"js/metadata-editor.underscore"
/>
<%
static
:
include
path
=
"js/metadata-editor.underscore"
/>
</script>
</script>
% for template_name in ["metadata-number-entry", "metadata-string-entry", "metadata-option-entry", "metadata-list-entry", "metadata-dict-entry"]:
% for template_name in ["metadata-number-entry", "metadata-string-entry", "metadata-option-entry", "metadata-list-entry", "metadata-dict-entry"
, "metadata-file-uploader-entry", "metadata-file-uploader-item"
]:
<script
id=
"${template_name}"
type=
"text/template"
>
<script
id=
"${template_name}"
type=
"text/template"
>
<%
static
:
include
path
=
"js/${template_name}.underscore"
/>
<%
static
:
include
path
=
"js/${template_name}.underscore"
/>
</script>
</script>
...
...
common/lib/xmodule/xmodule/css/video/display.scss
View file @
d861a9a0
...
@@ -42,8 +42,7 @@ div.video {
...
@@ -42,8 +42,7 @@ div.video {
margin
:
0
;
margin
:
0
;
padding
:
0
;
padding
:
0
;
.video-sources
,
.video-download-button
{
.video-tracks
{
display
:
inline-block
;
display
:
inline-block
;
vertical-align
:
top
;
vertical-align
:
top
;
margin
:
(
$baseline
*.
75
)
(
$baseline
/
2
)
0
0
;
margin
:
(
$baseline
*.
75
)
(
$baseline
/
2
)
0
0
;
...
...
common/lib/xmodule/xmodule/tests/test_video.py
View file @
d861a9a0
...
@@ -189,6 +189,7 @@ class VideoDescriptorImportTestCase(unittest.TestCase):
...
@@ -189,6 +189,7 @@ class VideoDescriptorImportTestCase(unittest.TestCase):
<source src="http://www.example.com/source.mp4"/>
<source src="http://www.example.com/source.mp4"/>
<source src="http://www.example.com/source.ogg"/>
<source src="http://www.example.com/source.ogg"/>
<track src="http://www.example.com/track"/>
<track src="http://www.example.com/track"/>
<handout src="http://www.example.com/handout"/>
<transcript language="ua" src="ukrainian_translation.srt" />
<transcript language="ua" src="ukrainian_translation.srt" />
<transcript language="ge" src="german_translation.srt" />
<transcript language="ge" src="german_translation.srt" />
</video>
</video>
...
@@ -211,6 +212,7 @@ class VideoDescriptorImportTestCase(unittest.TestCase):
...
@@ -211,6 +212,7 @@ class VideoDescriptorImportTestCase(unittest.TestCase):
'start_time'
:
datetime
.
timedelta
(
seconds
=
1
),
'start_time'
:
datetime
.
timedelta
(
seconds
=
1
),
'end_time'
:
datetime
.
timedelta
(
seconds
=
60
),
'end_time'
:
datetime
.
timedelta
(
seconds
=
60
),
'track'
:
'http://www.example.com/track'
,
'track'
:
'http://www.example.com/track'
,
'handout'
:
'http://www.example.com/handout'
,
'download_track'
:
True
,
'download_track'
:
True
,
'html5_sources'
:
[
'http://www.example.com/source.mp4'
,
'http://www.example.com/source.ogg'
],
'html5_sources'
:
[
'http://www.example.com/source.mp4'
,
'http://www.example.com/source.ogg'
],
'data'
:
''
,
'data'
:
''
,
...
@@ -229,6 +231,7 @@ class VideoDescriptorImportTestCase(unittest.TestCase):
...
@@ -229,6 +231,7 @@ class VideoDescriptorImportTestCase(unittest.TestCase):
end_time="00:01:00">
end_time="00:01:00">
<source src="http://www.example.com/source.mp4"/>
<source src="http://www.example.com/source.mp4"/>
<track src="http://www.example.com/track"/>
<track src="http://www.example.com/track"/>
<handout src="http://www.example.com/handout"/>
<transcript language="uk" src="ukrainian_translation.srt" />
<transcript language="uk" src="ukrainian_translation.srt" />
<transcript language="de" src="german_translation.srt" />
<transcript language="de" src="german_translation.srt" />
</video>
</video>
...
@@ -243,6 +246,7 @@ class VideoDescriptorImportTestCase(unittest.TestCase):
...
@@ -243,6 +246,7 @@ class VideoDescriptorImportTestCase(unittest.TestCase):
'start_time'
:
datetime
.
timedelta
(
seconds
=
1
),
'start_time'
:
datetime
.
timedelta
(
seconds
=
1
),
'end_time'
:
datetime
.
timedelta
(
seconds
=
60
),
'end_time'
:
datetime
.
timedelta
(
seconds
=
60
),
'track'
:
'http://www.example.com/track'
,
'track'
:
'http://www.example.com/track'
,
'handout'
:
'http://www.example.com/handout'
,
'download_track'
:
False
,
'download_track'
:
False
,
'download_video'
:
False
,
'download_video'
:
False
,
'html5_sources'
:
[
'http://www.example.com/source.mp4'
],
'html5_sources'
:
[
'http://www.example.com/source.mp4'
],
...
@@ -273,6 +277,7 @@ class VideoDescriptorImportTestCase(unittest.TestCase):
...
@@ -273,6 +277,7 @@ class VideoDescriptorImportTestCase(unittest.TestCase):
'start_time'
:
datetime
.
timedelta
(
seconds
=
0.0
),
'start_time'
:
datetime
.
timedelta
(
seconds
=
0.0
),
'end_time'
:
datetime
.
timedelta
(
seconds
=
0.0
),
'end_time'
:
datetime
.
timedelta
(
seconds
=
0.0
),
'track'
:
''
,
'track'
:
''
,
'handout'
:
None
,
'download_track'
:
False
,
'download_track'
:
False
,
'download_video'
:
True
,
'download_video'
:
True
,
'html5_sources'
:
[
'http://www.example.com/source.mp4'
],
'html5_sources'
:
[
'http://www.example.com/source.mp4'
],
...
@@ -326,6 +331,7 @@ class VideoDescriptorImportTestCase(unittest.TestCase):
...
@@ -326,6 +331,7 @@ class VideoDescriptorImportTestCase(unittest.TestCase):
'start_time'
:
datetime
.
timedelta
(
seconds
=
0.0
),
'start_time'
:
datetime
.
timedelta
(
seconds
=
0.0
),
'end_time'
:
datetime
.
timedelta
(
seconds
=
0.0
),
'end_time'
:
datetime
.
timedelta
(
seconds
=
0.0
),
'track'
:
''
,
'track'
:
''
,
'handout'
:
None
,
'download_track'
:
False
,
'download_track'
:
False
,
'download_video'
:
False
,
'download_video'
:
False
,
'html5_sources'
:
[],
'html5_sources'
:
[],
...
@@ -345,7 +351,8 @@ class VideoDescriptorImportTestCase(unittest.TestCase):
...
@@ -345,7 +351,8 @@ class VideoDescriptorImportTestCase(unittest.TestCase):
show_captions="false"
show_captions="false"
download_video="true"
download_video="true"
sub=""html5_subtitles""
sub=""html5_subtitles""
track=""http://download_track""
track=""http://www.example.com/track""
handout=""http://www.example.com/handout""
download_track="true"
download_track="true"
youtube_id_0_75=""OEoXaMPEzf65""
youtube_id_0_75=""OEoXaMPEzf65""
youtube_id_1_25=""OEoXaMPEzf125""
youtube_id_1_25=""OEoXaMPEzf125""
...
@@ -362,7 +369,8 @@ class VideoDescriptorImportTestCase(unittest.TestCase):
...
@@ -362,7 +369,8 @@ class VideoDescriptorImportTestCase(unittest.TestCase):
'show_captions'
:
False
,
'show_captions'
:
False
,
'start_time'
:
datetime
.
timedelta
(
seconds
=
0.0
),
'start_time'
:
datetime
.
timedelta
(
seconds
=
0.0
),
'end_time'
:
datetime
.
timedelta
(
seconds
=
0.0
),
'end_time'
:
datetime
.
timedelta
(
seconds
=
0.0
),
'track'
:
'http://download_track'
,
'track'
:
'http://www.example.com/track'
,
'handout'
:
'http://www.example.com/handout'
,
'download_track'
:
True
,
'download_track'
:
True
,
'download_video'
:
True
,
'download_video'
:
True
,
'html5_sources'
:
[
"source_1"
,
"source_2"
],
'html5_sources'
:
[
"source_1"
,
"source_2"
],
...
@@ -386,6 +394,7 @@ class VideoDescriptorImportTestCase(unittest.TestCase):
...
@@ -386,6 +394,7 @@ class VideoDescriptorImportTestCase(unittest.TestCase):
'start_time'
:
datetime
.
timedelta
(
seconds
=
0.0
),
'start_time'
:
datetime
.
timedelta
(
seconds
=
0.0
),
'end_time'
:
datetime
.
timedelta
(
seconds
=
0.0
),
'end_time'
:
datetime
.
timedelta
(
seconds
=
0.0
),
'track'
:
''
,
'track'
:
''
,
'handout'
:
None
,
'download_track'
:
False
,
'download_track'
:
False
,
'download_video'
:
False
,
'download_video'
:
False
,
'html5_sources'
:
[],
'html5_sources'
:
[],
...
@@ -509,6 +518,7 @@ class VideoExportTestCase(unittest.TestCase):
...
@@ -509,6 +518,7 @@ class VideoExportTestCase(unittest.TestCase):
desc
.
start_time
=
datetime
.
timedelta
(
seconds
=
1.0
)
desc
.
start_time
=
datetime
.
timedelta
(
seconds
=
1.0
)
desc
.
end_time
=
datetime
.
timedelta
(
seconds
=
60
)
desc
.
end_time
=
datetime
.
timedelta
(
seconds
=
60
)
desc
.
track
=
'http://www.example.com/track'
desc
.
track
=
'http://www.example.com/track'
desc
.
handout
=
'http://www.example.com/handout'
desc
.
download_track
=
True
desc
.
download_track
=
True
desc
.
html5_sources
=
[
'http://www.example.com/source.mp4'
,
'http://www.example.com/source.ogg'
]
desc
.
html5_sources
=
[
'http://www.example.com/source.mp4'
,
'http://www.example.com/source.ogg'
]
desc
.
download_video
=
True
desc
.
download_video
=
True
...
@@ -520,6 +530,7 @@ class VideoExportTestCase(unittest.TestCase):
...
@@ -520,6 +530,7 @@ class VideoExportTestCase(unittest.TestCase):
<source src="http://www.example.com/source.mp4"/>
<source src="http://www.example.com/source.mp4"/>
<source src="http://www.example.com/source.ogg"/>
<source src="http://www.example.com/source.ogg"/>
<track src="http://www.example.com/track"/>
<track src="http://www.example.com/track"/>
<handout src="http://www.example.com/handout"/>
<transcript language="ge" src="german_translation.srt" />
<transcript language="ge" src="german_translation.srt" />
<transcript language="ua" src="ukrainian_translation.srt" />
<transcript language="ua" src="ukrainian_translation.srt" />
</video>
</video>
...
...
common/lib/xmodule/xmodule/video_module/video_module.py
View file @
d861a9a0
...
@@ -143,6 +143,7 @@ class VideoModule(VideoFields, VideoStudentViewHandlers, XModule):
...
@@ -143,6 +143,7 @@ class VideoModule(VideoFields, VideoStudentViewHandlers, XModule):
'data_dir'
:
getattr
(
self
,
'data_dir'
,
None
),
'data_dir'
:
getattr
(
self
,
'data_dir'
,
None
),
'display_name'
:
self
.
display_name_with_default
,
'display_name'
:
self
.
display_name_with_default
,
'end'
:
self
.
end_time
.
total_seconds
(),
'end'
:
self
.
end_time
.
total_seconds
(),
'handout'
:
self
.
handout
,
'id'
:
self
.
location
.
html_id
(),
'id'
:
self
.
location
.
html_id
(),
'show_captions'
:
json
.
dumps
(
self
.
show_captions
),
'show_captions'
:
json
.
dumps
(
self
.
show_captions
),
'sources'
:
sources
,
'sources'
:
sources
,
...
@@ -248,6 +249,8 @@ class VideoDescriptor(VideoFields, VideoStudioViewHandlers, TabsEditingDescripto
...
@@ -248,6 +249,8 @@ class VideoDescriptor(VideoFields, VideoStudioViewHandlers, TabsEditingDescripto
editable_fields
[
'transcripts'
][
'languages'
]
=
languages
editable_fields
[
'transcripts'
][
'languages'
]
=
languages
editable_fields
[
'transcripts'
][
'type'
]
=
'VideoTranslations'
editable_fields
[
'transcripts'
][
'type'
]
=
'VideoTranslations'
editable_fields
[
'transcripts'
][
'urlRoot'
]
=
self
.
runtime
.
handler_url
(
self
,
'studio_transcript'
,
'translation'
)
.
rstrip
(
'/?'
)
editable_fields
[
'transcripts'
][
'urlRoot'
]
=
self
.
runtime
.
handler_url
(
self
,
'studio_transcript'
,
'translation'
)
.
rstrip
(
'/?'
)
editable_fields
[
'handout'
][
'type'
]
=
'FileUploader'
return
editable_fields
return
editable_fields
@classmethod
@classmethod
...
@@ -320,6 +323,11 @@ class VideoDescriptor(VideoFields, VideoStudioViewHandlers, TabsEditingDescripto
...
@@ -320,6 +323,11 @@ class VideoDescriptor(VideoFields, VideoStudioViewHandlers, TabsEditingDescripto
ele
.
set
(
'src'
,
self
.
track
)
ele
.
set
(
'src'
,
self
.
track
)
xml
.
append
(
ele
)
xml
.
append
(
ele
)
if
self
.
handout
:
ele
=
etree
.
Element
(
'handout'
)
ele
.
set
(
'src'
,
self
.
handout
)
xml
.
append
(
ele
)
# sorting for easy testing of resulting xml
# sorting for easy testing of resulting xml
for
transcript_language
in
sorted
(
self
.
transcripts
.
keys
()):
for
transcript_language
in
sorted
(
self
.
transcripts
.
keys
()):
ele
=
etree
.
Element
(
'transcript'
)
ele
=
etree
.
Element
(
'transcript'
)
...
@@ -422,6 +430,10 @@ class VideoDescriptor(VideoFields, VideoStudioViewHandlers, TabsEditingDescripto
...
@@ -422,6 +430,10 @@ class VideoDescriptor(VideoFields, VideoStudioViewHandlers, TabsEditingDescripto
if
track
is
not
None
:
if
track
is
not
None
:
field_data
[
'track'
]
=
track
.
get
(
'src'
)
field_data
[
'track'
]
=
track
.
get
(
'src'
)
handout
=
xml
.
find
(
'handout'
)
if
handout
is
not
None
:
field_data
[
'handout'
]
=
handout
.
get
(
'src'
)
transcripts
=
xml
.
findall
(
'transcript'
)
transcripts
=
xml
.
findall
(
'transcript'
)
if
transcripts
:
if
transcripts
:
field_data
[
'transcripts'
]
=
{
tr
.
get
(
'language'
):
tr
.
get
(
'src'
)
for
tr
in
transcripts
}
field_data
[
'transcripts'
]
=
{
tr
.
get
(
'language'
):
tr
.
get
(
'src'
)
for
tr
in
transcripts
}
...
...
common/lib/xmodule/xmodule/video_module/video_xfields.py
View file @
d861a9a0
...
@@ -15,8 +15,9 @@ class VideoFields(object):
...
@@ -15,8 +15,9 @@ class VideoFields(object):
default
=
"Video"
,
default
=
"Video"
,
scope
=
Scope
.
settings
scope
=
Scope
.
settings
)
)
saved_video_position
=
RelativeTime
(
saved_video_position
=
RelativeTime
(
help
=
"Current position in the video"
,
help
=
"Current position in the video
.
"
,
scope
=
Scope
.
user_state
,
scope
=
Scope
.
user_state
,
default
=
datetime
.
timedelta
(
seconds
=
0
)
default
=
datetime
.
timedelta
(
seconds
=
0
)
)
)
...
@@ -105,13 +106,13 @@ class VideoFields(object):
...
@@ -105,13 +106,13 @@ class VideoFields(object):
)
)
# Data format: {'de': 'german_translation', 'uk': 'ukrainian_translation'}
# Data format: {'de': 'german_translation', 'uk': 'ukrainian_translation'}
transcripts
=
Dict
(
transcripts
=
Dict
(
help
=
"Add additional transcripts in other languages"
,
help
=
"Add additional transcripts in other languages
.
"
,
display_name
=
"Transcript Translations"
,
display_name
=
"Transcript Translations"
,
scope
=
Scope
.
settings
,
scope
=
Scope
.
settings
,
default
=
{}
default
=
{}
)
)
transcript_language
=
String
(
transcript_language
=
String
(
help
=
"Preferred language for transcript"
,
help
=
"Preferred language for transcript
.
"
,
display_name
=
"Preferred language for transcript"
,
display_name
=
"Preferred language for transcript"
,
scope
=
Scope
.
preferences
,
scope
=
Scope
.
preferences
,
default
=
"en"
default
=
"en"
...
@@ -130,12 +131,18 @@ class VideoFields(object):
...
@@ -130,12 +131,18 @@ class VideoFields(object):
scope
=
Scope
.
user_state
,
scope
=
Scope
.
user_state
,
)
)
global_speed
=
Float
(
global_speed
=
Float
(
help
=
"Default speed in cases when speed wasn't explicitly for specific video"
,
help
=
"Default speed in cases when speed wasn't explicitly for specific video
.
"
,
scope
=
Scope
.
preferences
,
scope
=
Scope
.
preferences
,
default
=
1.0
default
=
1.0
)
)
youtube_is_available
=
Boolean
(
youtube_is_available
=
Boolean
(
help
=
"The availaibility of YouTube API for the user"
,
help
=
"The availaibility of YouTube API for the user
.
"
,
scope
=
Scope
.
user_info
,
scope
=
Scope
.
user_info
,
default
=
True
default
=
True
)
)
handout
=
String
(
help
=
"Upload a handout to accompany this video. Students can download the handout by clicking Download Handout under the video."
,
display_name
=
"Upload Handout"
,
scope
=
Scope
.
settings
,
)
lms/djangoapps/courseware/tests/test_video_mongo.py
View file @
d861a9a0
...
@@ -41,6 +41,7 @@ class TestVideoYouTube(TestVideo):
...
@@ -41,6 +41,7 @@ class TestVideoYouTube(TestVideo):
'end'
:
3610.0
,
'end'
:
3610.0
,
'id'
:
self
.
item_descriptor
.
location
.
html_id
(),
'id'
:
self
.
item_descriptor
.
location
.
html_id
(),
'show_captions'
:
'true'
,
'show_captions'
:
'true'
,
'handout'
:
None
,
'sources'
:
sources
,
'sources'
:
sources
,
'speed'
:
'null'
,
'speed'
:
'null'
,
'general_speed'
:
1.0
,
'general_speed'
:
1.0
,
...
@@ -104,6 +105,7 @@ class TestVideoNonYouTube(TestVideo):
...
@@ -104,6 +105,7 @@ class TestVideoNonYouTube(TestVideo):
'ajax_url'
:
self
.
item_descriptor
.
xmodule_runtime
.
ajax_url
+
'/save_user_state'
,
'ajax_url'
:
self
.
item_descriptor
.
xmodule_runtime
.
ajax_url
+
'/save_user_state'
,
'data_dir'
:
getattr
(
self
,
'data_dir'
,
None
),
'data_dir'
:
getattr
(
self
,
'data_dir'
,
None
),
'show_captions'
:
'true'
,
'show_captions'
:
'true'
,
'handout'
:
None
,
'display_name'
:
u'A Name'
,
'display_name'
:
u'A Name'
,
'end'
:
3610.0
,
'end'
:
3610.0
,
'id'
:
self
.
item_descriptor
.
location
.
html_id
(),
'id'
:
self
.
item_descriptor
.
location
.
html_id
(),
...
@@ -203,6 +205,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
...
@@ -203,6 +205,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
expected_context
=
{
expected_context
=
{
'data_dir'
:
getattr
(
self
,
'data_dir'
,
None
),
'data_dir'
:
getattr
(
self
,
'data_dir'
,
None
),
'show_captions'
:
'true'
,
'show_captions'
:
'true'
,
'handout'
:
None
,
'display_name'
:
u'A Name'
,
'display_name'
:
u'A Name'
,
'end'
:
3610.0
,
'end'
:
3610.0
,
'id'
:
None
,
'id'
:
None
,
...
@@ -324,6 +327,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
...
@@ -324,6 +327,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
expected_context
=
{
expected_context
=
{
'data_dir'
:
getattr
(
self
,
'data_dir'
,
None
),
'data_dir'
:
getattr
(
self
,
'data_dir'
,
None
),
'show_captions'
:
'true'
,
'show_captions'
:
'true'
,
'handout'
:
None
,
'display_name'
:
u'A Name'
,
'display_name'
:
u'A Name'
,
'end'
:
3610.0
,
'end'
:
3610.0
,
'id'
:
None
,
'id'
:
None
,
...
@@ -469,6 +473,7 @@ class TestVideoDescriptorInitialization(BaseTestXmodule):
...
@@ -469,6 +473,7 @@ class TestVideoDescriptorInitialization(BaseTestXmodule):
'options'
:
[],
'options'
:
[],
},
},
'transcripts'
:
{},
'transcripts'
:
{},
'handout'
:
{},
}
}
):
):
metadata
=
{
metadata
=
{
...
...
lms/templates/video.html
View file @
d861a9a0
...
@@ -107,12 +107,12 @@
...
@@ -107,12 +107,12 @@
<div
class=
"focus_grabber last"
></div>
<div
class=
"focus_grabber last"
></div>
<ul
class=
"wrapper-downloads"
>
<ul
class=
"wrapper-downloads"
>
% if sources.get('main'):
% if sources.get('main'):
<li
class=
"video-sources"
>
<li
class=
"video-sources
video-download-button
"
>
${('
<a
href=
"%s"
>
' + _('Download video') + '
</a>
') % sources.get('main')}
${('
<a
href=
"%s"
>
' + _('Download video') + '
</a>
') % sources.get('main')}
</li>
</li>
% endif
% endif
% if track:
% if track:
<li
class=
"video-tracks"
>
<li
class=
"video-tracks
video-download-button
"
>
% if transcript_download_format:
% if transcript_download_format:
${('
<a
href=
"%s"
>
' + _('Download transcript') + '
</a>
') % track
${('
<a
href=
"%s"
>
' + _('Download transcript') + '
</a>
') % track
}
}
...
@@ -138,5 +138,10 @@
...
@@ -138,5 +138,10 @@
% endif
% endif
</li>
</li>
% endif
% endif
% if handout:
<li
class=
"video-handout video-download-button"
>
${('
<a
href=
"%s"
target=
"_blank"
>
' + _('Download Handout') + '
</a>
') % handout}
</li>
% endif
</ul>
</ul>
</div>
</div>
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