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
4a36fa89
Commit
4a36fa89
authored
Dec 26, 2013
by
polesye
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
BLD-368: Turn "download transcript" into a dropdown.
parent
2536224b
Show whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
508 additions
and
44 deletions
+508
-44
CHANGELOG.rst
+3
-0
cms/djangoapps/contentstore/features/component_settings_editor_helpers.py
+1
-1
cms/djangoapps/contentstore/features/video-editor.py
+1
-1
cms/static/js/views/metadata.js
+13
-0
cms/static/sass/views/_unit.scss
+6
-0
common/lib/xmodule/xmodule/tests/test_video.py
+17
-5
common/lib/xmodule/xmodule/video_module.py
+99
-4
lms/djangoapps/courseware/tests/__init__.py
+30
-22
lms/djangoapps/courseware/tests/test_video_mongo.py
+337
-7
lms/djangoapps/courseware/tests/test_video_xml.py
+1
-4
No files found.
CHANGELOG.rst
View file @
4a36fa89
...
...
@@ -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
the top. Include a label indicating the component affected.
Blades: Change the track field to a dropdown that will allow students
to download the transcript of the video without timecodes. BLD-368.
Blades: Video player start-end time range is now shown even before Play is
clicked. Video player VCR time shows correct non-zero total time for YouTube
videos even before Play is clicked. BLD-529.
...
...
cms/djangoapps/contentstore/features/component_settings_editor_helpers.py
View file @
4a36fa89
...
...
@@ -140,7 +140,7 @@ def verify_setting_entry(setting, display_name, value, explicitly_set):
for the problem, rather than derived from the defaults. This is verified
by the existence of a "Clear" button next to the field value.
"""
assert_equal
(
display_name
,
setting
.
find_by_css
(
'.setting-label'
)[
0
]
.
html
)
assert_equal
(
display_name
,
setting
.
find_by_css
(
'.setting-label'
)[
0
]
.
html
.
strip
()
)
# Check if the web object is a list type
# If so, we use a slightly different mechanism for determining its value
...
...
cms/djangoapps/contentstore/features/video-editor.py
View file @
4a36fa89
...
...
@@ -40,12 +40,12 @@ def correct_video_settings(_step):
# advanced
[
'Display Name'
,
'Video'
,
False
],
[
'Download Transcript'
,
''
,
False
],
[
'Download Video'
,
''
,
False
],
[
'End Time'
,
'00:00:00'
,
False
],
[
'HTML5 Transcript'
,
''
,
False
],
[
'Show Transcript'
,
'True'
,
False
],
[
'Start Time'
,
'00:00:00'
,
False
],
[
'Transcript Download Allowed'
,
'False'
,
False
],
[
'Video Sources'
,
''
,
False
],
[
'Youtube ID'
,
'OEoXaMPEzfM'
,
False
],
[
'Youtube ID for .75x speed'
,
''
,
False
],
...
...
cms/static/js/views/metadata.js
View file @
4a36fa89
...
...
@@ -94,6 +94,19 @@ function(BaseView, _, MetadataModel, AbstractEditor, VideoList) {
templateName
:
"metadata-string-entry"
,
render
:
function
()
{
AbstractEditor
.
prototype
.
render
.
apply
(
this
);
// If the model has property `non editable` equals `true`,
// the field is disabled, but user is able to clear it.
if
(
this
.
model
.
get
(
'non_editable'
))
{
this
.
$el
.
find
(
'#'
+
this
.
uniqueId
)
.
prop
(
'readonly'
,
true
)
.
addClass
(
'is-disabled'
);
}
},
getValueFromEditor
:
function
()
{
return
this
.
$el
.
find
(
'#'
+
this
.
uniqueId
).
val
();
},
...
...
cms/static/sass/views/_unit.scss
View file @
4a36fa89
...
...
@@ -708,6 +708,12 @@ body.course.unit,.view-unit {
text-overflow
:
ellipsis
;
}
//Allows users to copy full value of disabled inputs.
input
.is-disabled
{
text-overflow
:
clip
;
opacity
:
.5
;
}
input
[
type
=
"number"
]
{
width
:
38
.5%
;
...
...
common/lib/xmodule/xmodule/tests/test_video.py
View file @
4a36fa89
...
...
@@ -25,7 +25,6 @@ from .test_import import DummySystem
from
xblock.field_data
import
DictFieldData
from
xblock.fields
import
ScopeIds
from
textwrap
import
dedent
from
xmodule.tests
import
get_test_descriptor_system
...
...
@@ -187,6 +186,7 @@ class VideoDescriptorImportTestCase(unittest.TestCase):
<video display_name="Test Video"
youtube="1.0:p2Q6BrNhdh8,0.75:izygArpw-Qo,1.25:1EeWXzPdhSA,1.5:rABDYkeK0x8"
show_captions="false"
download_track="true"
start_time="00:00:01"
end_time="00:01:00">
<source src="http://www.example.com/source.mp4"/>
...
...
@@ -211,6 +211,7 @@ class VideoDescriptorImportTestCase(unittest.TestCase):
'start_time'
:
datetime
.
timedelta
(
seconds
=
1
),
'end_time'
:
datetime
.
timedelta
(
seconds
=
60
),
'track'
:
'http://www.example.com/track'
,
'download_track'
:
True
,
'html5_sources'
:
[
'http://www.example.com/source.mp4'
,
'http://www.example.com/source.ogg'
],
'data'
:
''
})
...
...
@@ -221,6 +222,7 @@ class VideoDescriptorImportTestCase(unittest.TestCase):
<video display_name="Test Video"
youtube="1.0:p2Q6BrNhdh8,0.75:izygArpw-Qo,1.25:1EeWXzPdhSA,1.5:rABDYkeK0x8"
show_captions="false"
download_track="false"
start_time="00:00:01"
end_time="00:01:00">
<source src="http://www.example.com/source.mp4"/>
...
...
@@ -237,6 +239,7 @@ class VideoDescriptorImportTestCase(unittest.TestCase):
'start_time'
:
datetime
.
timedelta
(
seconds
=
1
),
'end_time'
:
datetime
.
timedelta
(
seconds
=
60
),
'track'
:
'http://www.example.com/track'
,
'download_track'
:
False
,
'source'
:
'http://www.example.com/source.mp4'
,
'html5_sources'
:
[
'http://www.example.com/source.mp4'
],
'data'
:
''
...
...
@@ -253,7 +256,6 @@ class VideoDescriptorImportTestCase(unittest.TestCase):
youtube="1.0:p2Q6BrNhdh8,1.25:1EeWXzPdhSA"
show_captions="true">
<source src="http://www.example.com/source.mp4"/>
<track src="http://www.example.com/track"/>
</video>
'''
output
=
VideoDescriptor
.
from_xml
(
xml_data
,
module_system
,
Mock
())
...
...
@@ -265,7 +267,8 @@ class VideoDescriptorImportTestCase(unittest.TestCase):
'show_captions'
:
True
,
'start_time'
:
datetime
.
timedelta
(
seconds
=
0.0
),
'end_time'
:
datetime
.
timedelta
(
seconds
=
0.0
),
'track'
:
'http://www.example.com/track'
,
'track'
:
''
,
'download_track'
:
False
,
'source'
:
'http://www.example.com/source.mp4'
,
'html5_sources'
:
[
'http://www.example.com/source.mp4'
],
'data'
:
''
...
...
@@ -287,6 +290,7 @@ class VideoDescriptorImportTestCase(unittest.TestCase):
'start_time'
:
datetime
.
timedelta
(
seconds
=
0.0
),
'end_time'
:
datetime
.
timedelta
(
seconds
=
0.0
),
'track'
:
''
,
'download_track'
:
False
,
'source'
:
''
,
'html5_sources'
:
[],
'data'
:
''
...
...
@@ -305,6 +309,7 @@ class VideoDescriptorImportTestCase(unittest.TestCase):
source=""http://download_video""
sub=""html5_subtitles""
track=""http://download_track""
download_track="true"
youtube_id_0_75=""OEoXaMPEzf65""
youtube_id_1_25=""OEoXaMPEzf125""
youtube_id_1_5=""OEoXaMPEzf15""
...
...
@@ -321,6 +326,7 @@ class VideoDescriptorImportTestCase(unittest.TestCase):
'start_time'
:
datetime
.
timedelta
(
seconds
=
0.0
),
'end_time'
:
datetime
.
timedelta
(
seconds
=
0.0
),
'track'
:
'http://download_track'
,
'download_track'
:
True
,
'source'
:
'http://download_video'
,
'html5_sources'
:
[
"source_1"
,
"source_2"
],
'data'
:
''
...
...
@@ -343,6 +349,7 @@ class VideoDescriptorImportTestCase(unittest.TestCase):
'start_time'
:
datetime
.
timedelta
(
seconds
=
0.0
),
'end_time'
:
datetime
.
timedelta
(
seconds
=
0.0
),
'track'
:
''
,
'download_track'
:
False
,
'source'
:
''
,
'html5_sources'
:
[],
'data'
:
''
...
...
@@ -373,6 +380,7 @@ class VideoDescriptorImportTestCase(unittest.TestCase):
'start_time'
:
datetime
.
timedelta
(
seconds
=
1
),
'end_time'
:
datetime
.
timedelta
(
seconds
=
60
),
'track'
:
'http://www.example.com/track'
,
'download_track'
:
True
,
'html5_sources'
:
[
'http://www.example.com/source.mp4'
],
'data'
:
''
})
...
...
@@ -402,6 +410,7 @@ class VideoDescriptorImportTestCase(unittest.TestCase):
'start_time'
:
datetime
.
timedelta
(
seconds
=
1
),
'end_time'
:
datetime
.
timedelta
(
seconds
=
60
),
'track'
:
'http://www.example.com/track'
,
'download_track'
:
True
,
'html5_sources'
:
[
'http://www.example.com/source.mp4'
],
'data'
:
''
})
...
...
@@ -431,6 +440,7 @@ class VideoDescriptorImportTestCase(unittest.TestCase):
'start_time'
:
datetime
.
timedelta
(
seconds
=
1
),
'end_time'
:
datetime
.
timedelta
(
seconds
=
60
),
'track'
:
'http://www.example.com/track'
,
'download_track'
:
True
,
'html5_sources'
:
[
'http://www.example.com/source.mp4'
],
'data'
:
''
})
...
...
@@ -461,11 +471,12 @@ class VideoExportTestCase(unittest.TestCase):
desc
.
start_time
=
datetime
.
timedelta
(
seconds
=
1.0
)
desc
.
end_time
=
datetime
.
timedelta
(
seconds
=
60
)
desc
.
track
=
'http://www.example.com/track'
desc
.
download_track
=
True
desc
.
html5_sources
=
[
'http://www.example.com/source.mp4'
,
'http://www.example.com/source.ogg'
]
xml
=
desc
.
definition_to_xml
(
None
)
# We don't use the `resource_fs` parameter
expected
=
etree
.
fromstring
(
'''
\
<video url_name="SampleProblem1" start_time="0:00:01" youtube="0.75:izygArpw-Qo,1.00:p2Q6BrNhdh8,1.25:1EeWXzPdhSA,1.50:rABDYkeK0x8" show_captions="false" end_time="0:01:00">
<video url_name="SampleProblem1" start_time="0:00:01" youtube="0.75:izygArpw-Qo,1.00:p2Q6BrNhdh8,1.25:1EeWXzPdhSA,1.50:rABDYkeK0x8" show_captions="false" end_time="0:01:00"
download_track="true"
>
<source src="http://www.example.com/source.mp4"/>
<source src="http://www.example.com/source.ogg"/>
<track src="http://www.example.com/track"/>
...
...
@@ -488,11 +499,12 @@ class VideoExportTestCase(unittest.TestCase):
desc
.
start_time
=
datetime
.
timedelta
(
seconds
=
5.0
)
desc
.
end_time
=
datetime
.
timedelta
(
seconds
=
0.0
)
desc
.
track
=
'http://www.example.com/track'
desc
.
download_track
=
True
desc
.
html5_sources
=
[
'http://www.example.com/source.mp4'
,
'http://www.example.com/source.ogg'
]
xml
=
desc
.
definition_to_xml
(
None
)
# We don't use the `resource_fs` parameter
expected
=
etree
.
fromstring
(
'''
\
<video url_name="SampleProblem1" start_time="0:00:05" youtube="0.75:izygArpw-Qo,1.00:p2Q6BrNhdh8,1.25:1EeWXzPdhSA,1.50:rABDYkeK0x8" show_captions="false">
<video url_name="SampleProblem1" start_time="0:00:05" youtube="0.75:izygArpw-Qo,1.00:p2Q6BrNhdh8,1.25:1EeWXzPdhSA,1.50:rABDYkeK0x8" show_captions="false"
download_track="true"
>
<source src="http://www.example.com/source.mp4"/>
<source src="http://www.example.com/source.ogg"/>
<track src="http://www.example.com/track"/>
...
...
common/lib/xmodule/xmodule/video_module.py
View file @
4a36fa89
...
...
@@ -13,18 +13,24 @@ in XML.
import
json
import
logging
from
HTMLParser
import
HTMLParser
from
lxml
import
etree
from
pkg_resources
import
resource_string
import
datetime
import
copy
from
webob
import
Response
from
django.http
import
Http404
from
django.conf
import
settings
from
xmodule.x_module
import
XModule
from
xmodule.x_module
import
XModule
,
module_attr
from
xmodule.editing_module
import
TabsEditingDescriptor
from
xmodule.raw_module
import
EmptyDataRawDescriptor
from
xmodule.xml_module
import
is_pointer_tag
,
name_to_pathname
,
deserialize_field
from
xmodule.contentstore.django
import
contentstore
from
xmodule.contentstore.content
import
StaticContent
from
xmodule.exceptions
import
NotFoundError
from
xblock.core
import
XBlock
from
xblock.fields
import
Scope
,
String
,
Boolean
,
List
,
Integer
,
ScopeIds
from
xmodule.fields
import
RelativeTime
...
...
@@ -103,11 +109,19 @@ class VideoFields(object):
display_name
=
"Video Sources"
,
scope
=
Scope
.
settings
,
)
# `track` is deprecated field and should not be used in future.
# `download_track` is used instead.
track
=
String
(
help
=
"The external URL to download the timed transcript track.
This appears as a link beneath the video.
"
,
help
=
"The external URL to download the timed transcript track."
,
display_name
=
"Download Transcript"
,
scope
=
Scope
.
settings
,
default
=
""
default
=
''
)
download_track
=
Boolean
(
help
=
"Show a link beneath the video to allow students to download the transcript. Note: You must add a link to the HTML5 Transcript field above."
,
display_name
=
"Transcript Download Allowed"
,
scope
=
Scope
.
settings
,
default
=
False
)
sub
=
String
(
help
=
"The name of the timed transcript track (for non-Youtube videos)."
,
...
...
@@ -162,18 +176,25 @@ class VideoModule(VideoFields, XModule):
raise
Http404
()
def
get_html
(
self
):
track_url
=
None
caption_asset_path
=
"/static/subs/"
get_ext
=
lambda
filename
:
filename
.
rpartition
(
'.'
)[
-
1
]
sources
=
{
get_ext
(
src
):
src
for
src
in
self
.
html5_sources
}
sources
[
'main'
]
=
self
.
source
if
self
.
download_track
:
if
self
.
track
:
track_url
=
self
.
track
elif
self
.
sub
:
track_url
=
self
.
runtime
.
handler_url
(
self
,
'download_transcript'
)
return
self
.
system
.
render_template
(
'video.html'
,
{
'youtube_streams'
:
_create_youtube_string
(
self
),
'id'
:
self
.
location
.
html_id
(),
'sub'
:
self
.
sub
,
'sources'
:
sources
,
'track'
:
self
.
track
,
'track'
:
track_url
,
'display_name'
:
self
.
display_name_with_default
,
# This won't work when we move to data that
# isn't on the filesystem
...
...
@@ -189,10 +210,58 @@ class VideoModule(VideoFields, XModule):
'yt_test_url'
:
settings
.
YOUTUBE_TEST_URL
})
def
get_transcript
(
self
,
subs_id
):
'''
Returns transcript without timecodes.
Args:
`subs_id`: str, subtitles id
Raises:
- NotFoundError if cannot find transcript file in storage.
- ValueError if transcript file is incorrect JSON.
- KeyError if transcript file has incorrect format.
'''
filename
=
'subs_{0}.srt.sjson'
.
format
(
subs_id
)
content_location
=
StaticContent
.
compute_location
(
self
.
location
.
org
,
self
.
location
.
course
,
filename
)
data
=
contentstore
()
.
find
(
content_location
)
.
data
text
=
json
.
loads
(
data
)[
'text'
]
return
HTMLParser
()
.
unescape
(
"
\n
"
.
join
(
text
))
@XBlock.handler
def
download_transcript
(
self
,
__
,
___
):
"""
This is called to get transcript file without timecodes to student.
"""
try
:
subs
=
self
.
get_transcript
(
self
.
sub
)
except
(
NotFoundError
):
log
.
debug
(
"Can't find content in storage for
%
s transcript"
,
self
.
sub
)
return
Response
(
status
=
404
)
except
(
ValueError
,
KeyError
):
log
.
debug
(
"Invalid transcript JSON."
)
return
Response
(
status
=
400
)
response
=
Response
(
subs
,
headerlist
=
[
(
'Content-Disposition'
,
'attachment; filename="{0}.txt"'
.
format
(
self
.
sub
)),
])
response
.
content_type
=
"text/plain; charset=utf-8"
return
response
class
VideoDescriptor
(
VideoFields
,
TabsEditingDescriptor
,
EmptyDataRawDescriptor
):
"""Descriptor for `VideoModule`."""
module_class
=
VideoModule
download_transcript
=
module_attr
(
'download_transcript'
)
tabs
=
[
{
...
...
@@ -207,6 +276,12 @@ class VideoDescriptor(VideoFields, TabsEditingDescriptor, EmptyDataRawDescriptor
]
def
__init__
(
self
,
*
args
,
**
kwargs
):
'''
`track` is deprecated field.
If `track` field exists show `track` field on front-end as not-editable
but clearable. Dropdown `download_track` is a new field and it has value
True.
'''
super
(
VideoDescriptor
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
# For backwards compatibility -- if we've got XML data, parse
# it out and set the metadata fields
...
...
@@ -215,6 +290,24 @@ class VideoDescriptor(VideoFields, TabsEditingDescriptor, EmptyDataRawDescriptor
self
.
_field_data
.
set_many
(
self
,
field_data
)
del
self
.
data
self
.
track_visible
=
False
if
self
.
track
:
self
.
track_visible
=
True
download_track
=
self
.
editable_metadata_fields
[
'download_track'
]
if
not
download_track
[
'explicitly_set'
]:
self
.
download_track
=
True
@property
def
editable_metadata_fields
(
self
):
editable_fields
=
super
(
VideoDescriptor
,
self
)
.
editable_metadata_fields
if
self
.
track_visible
:
editable_fields
[
'track'
][
'non_editable'
]
=
True
else
:
editable_fields
.
pop
(
'track'
)
return
editable_fields
@classmethod
def
from_xml
(
cls
,
xml_data
,
system
,
id_generator
):
"""
...
...
@@ -265,6 +358,7 @@ class VideoDescriptor(VideoFields, TabsEditingDescriptor, EmptyDataRawDescriptor
'start_time'
:
self
.
start_time
,
'end_time'
:
self
.
end_time
,
'sub'
:
self
.
sub
,
'download_track'
:
json
.
dumps
(
self
.
download_track
),
}
for
key
,
value
in
attrs
.
items
():
# Mild workaround to ensure that tests pass -- if a field
...
...
@@ -282,6 +376,7 @@ class VideoDescriptor(VideoFields, TabsEditingDescriptor, EmptyDataRawDescriptor
ele
=
etree
.
Element
(
'track'
)
ele
.
set
(
'src'
,
self
.
track
)
xml
.
append
(
ele
)
return
xml
def
get_context
(
self
):
...
...
lms/djangoapps/courseware/tests/__init__.py
View file @
4a36fa89
...
...
@@ -15,7 +15,6 @@ from edxmako.shortcuts import render_to_string
from
student.tests.factories
import
UserFactory
,
CourseEnrollmentFactory
from
courseware.tests.modulestore_config
import
TEST_DATA_MIXED_MODULESTORE
from
xblock.field_data
import
DictFieldData
from
xblock.fields
import
Scope
from
xmodule.tests
import
get_test_system
,
get_test_descriptor_system
from
xmodule.modulestore
import
Location
from
xmodule.modulestore.django
import
modulestore
...
...
@@ -35,7 +34,7 @@ class BaseTestXmodule(ModuleStoreTestCase):
Any xmodule should overwrite only next parameters for test:
1. CATEGORY
2. DATA
2. DATA
or METADATA
3. MODEL_DATA
4. COURSE_DATA and USER_COUNT if needed
...
...
@@ -48,6 +47,10 @@ class BaseTestXmodule(ModuleStoreTestCase):
# Data from YAML common/lib/xmodule/xmodule/templates/NAME/default.yaml
CATEGORY
=
"vertical"
DATA
=
''
# METADATA must be overwritten for every instance that uses it. Otherwise,
# if we'll change it in the tests, it will be changed for all other instances
# of parent class.
METADATA
=
{}
MODEL_DATA
=
{
'data'
:
'<some_module></some_module>'
}
def
new_module_runtime
(
self
):
...
...
@@ -71,8 +74,27 @@ class BaseTestXmodule(ModuleStoreTestCase):
runtime
.
get_block
=
modulestore
()
.
get_item
return
runtime
def
setUp
(
self
):
def
initialize_module
(
self
,
**
kwargs
):
kwargs
.
update
({
'parent_location'
:
self
.
section
.
location
,
'category'
:
self
.
CATEGORY
})
self
.
item_descriptor
=
ItemFactory
.
create
(
**
kwargs
)
self
.
runtime
=
self
.
new_descriptor_runtime
()
field_data
=
{}
field_data
.
update
(
self
.
MODEL_DATA
)
student_data
=
DictFieldData
(
field_data
)
self
.
item_descriptor
.
_field_data
=
LmsFieldData
(
self
.
item_descriptor
.
_field_data
,
student_data
)
self
.
item_descriptor
.
xmodule_runtime
=
self
.
new_module_runtime
()
self
.
item_module
=
self
.
item_descriptor
self
.
item_url
=
Location
(
self
.
item_module
.
location
)
.
url
()
def
setup_course
(
self
):
self
.
course
=
CourseFactory
.
create
(
data
=
self
.
COURSE_DATA
)
# Turn off cache.
...
...
@@ -83,7 +105,7 @@ class BaseTestXmodule(ModuleStoreTestCase):
parent_location
=
self
.
course
.
location
,
category
=
"sequential"
,
)
section
=
ItemFactory
.
create
(
se
lf
.
se
ction
=
ItemFactory
.
create
(
parent_location
=
chapter
.
location
,
category
=
"sequential"
)
...
...
@@ -97,24 +119,6 @@ class BaseTestXmodule(ModuleStoreTestCase):
for
user
in
self
.
users
:
CourseEnrollmentFactory
.
create
(
user
=
user
,
course_id
=
self
.
course
.
id
)
self
.
item_descriptor
=
ItemFactory
.
create
(
parent_location
=
section
.
location
,
category
=
self
.
CATEGORY
,
data
=
self
.
DATA
)
self
.
runtime
=
self
.
new_descriptor_runtime
()
field_data
=
{}
field_data
.
update
(
self
.
MODEL_DATA
)
student_data
=
DictFieldData
(
field_data
)
self
.
item_descriptor
.
_field_data
=
LmsFieldData
(
self
.
item_descriptor
.
_field_data
,
student_data
)
self
.
item_descriptor
.
xmodule_runtime
=
self
.
new_module_runtime
()
self
.
item_module
=
self
.
item_descriptor
self
.
item_url
=
Location
(
self
.
item_module
.
location
)
.
url
()
# login all users for acces to Xmodule
self
.
clients
=
{
user
.
username
:
Client
()
for
user
in
self
.
users
}
self
.
login_statuses
=
[
...
...
@@ -125,6 +129,10 @@ class BaseTestXmodule(ModuleStoreTestCase):
self
.
assertTrue
(
all
(
self
.
login_statuses
))
def
setUp
(
self
):
self
.
setup_course
();
self
.
initialize_module
(
metadata
=
self
.
METADATA
,
data
=
self
.
DATA
)
def
get_url
(
self
,
dispatch
):
"""Return item url with dispatch."""
return
reverse
(
...
...
lms/djangoapps/courseware/tests/test_video_mongo.py
View file @
4a36fa89
# -*- coding: utf-8 -*-
"""Video xmodule tests in mongo."""
from
mock
import
patch
,
PropertyMock
import
os
import
tempfile
import
textwrap
from
functools
import
partial
from
xmodule.contentstore.content
import
StaticContent
from
xmodule.modulestore
import
Location
from
xmodule.contentstore.django
import
contentstore
from
.
import
BaseTestXmodule
from
.test_video_xml
import
SOURCE_XML
from
django.conf
import
settings
from
xmodule.video_module
import
_create_youtube_string
from
cache_toolbox.core
import
del_cached_content
from
xmodule.exceptions
import
NotFoundError
class
TestVideo
(
BaseTestXmodule
):
"""Integration tests: web client + mongo."""
CATEGORY
=
"video"
DATA
=
SOURCE_XML
METADATA
=
{}
def
test_handle_ajax_dispatch
(
self
):
responses
=
{
...
...
@@ -29,16 +39,21 @@ class TestVideo(BaseTestXmodule):
])
.
pop
(),
404
)
def
tearDown
(
self
):
_clear_assets
(
self
.
item_module
.
location
)
class
TestVideoYouTube
(
TestVideo
):
METADATA
=
{}
def
test_video_constructor
(
self
):
"""Make sure that all parameters extracted correclty from xml"""
context
=
self
.
item_module
.
render
(
'student_view'
)
.
content
sources
=
{
'main'
:
u'example.mp4'
,
u'mp4'
:
u'example.mp4'
,
u'webm'
:
u'example.webm'
,
u'ogv'
:
u'example.ogv'
}
expected_context
=
{
...
...
@@ -51,7 +66,7 @@ class TestVideo(BaseTestXmodule):
'sources'
:
sources
,
'start'
:
3603.0
,
'sub'
:
u'a_sub_file.srt.sjson'
,
'track'
:
''
,
'track'
:
None
,
'youtube_streams'
:
_create_youtube_string
(
self
.
item_module
),
'autoplay'
:
settings
.
FEATURES
.
get
(
'AUTOPLAY_VIDEOS'
,
False
),
'yt_test_timeout'
:
1500
,
...
...
@@ -75,12 +90,12 @@ class TestVideoNonYouTube(TestVideo):
>
<source src="example.mp4"/>
<source src="example.webm"/>
<source src="example.ogv"/>
</video>
"""
MODEL_DATA
=
{
'data'
:
DATA
}
METADATA
=
{}
def
test_video_constructor
(
self
):
"""Make sure that if the 'youtube' attribute is omitted in XML, then
...
...
@@ -90,7 +105,6 @@ class TestVideoNonYouTube(TestVideo):
'main'
:
u'example.mp4'
,
u'mp4'
:
u'example.mp4'
,
u'webm'
:
u'example.webm'
,
u'ogv'
:
u'example.ogv'
}
context
=
self
.
item_module
.
render
(
'student_view'
)
.
content
...
...
@@ -105,6 +119,178 @@ class TestVideoNonYouTube(TestVideo):
'sources'
:
sources
,
'start'
:
3603.0
,
'sub'
:
u'a_sub_file.srt.sjson'
,
'track'
:
None
,
'youtube_streams'
:
'1.00:OEoXaMPEzfM'
,
'autoplay'
:
settings
.
FEATURES
.
get
(
'AUTOPLAY_VIDEOS'
,
True
),
'yt_test_timeout'
:
1500
,
'yt_test_url'
:
'https://gdata.youtube.com/feeds/api/videos/'
}
self
.
assertEqual
(
context
,
self
.
item_module
.
xmodule_runtime
.
render_template
(
'video.html'
,
expected_context
)
)
class
TestVideoGetTranscriptsMethod
(
TestVideo
):
"""
Make sure that `get_transcript` method works correctly
"""
DATA
=
"""
<video show_captions="true"
display_name="A Name"
>
<source src="example.mp4"/>
<source src="example.webm"/>
</video>
"""
MODEL_DATA
=
{
'data'
:
DATA
}
METADATA
=
{}
def
test_good_transcript
(
self
):
self
.
item_module
.
render
(
'student_view'
)
item
=
self
.
item_descriptor
.
xmodule_runtime
.
xmodule_instance
good_sjson
=
_create_file
(
content
=
"""
{
"start": [
270,
2720
],
"end": [
2720,
5430
],
"text": [
"Hi, welcome to Edx.",
"Let's start with what is on your screen right now."
]
}
"""
)
_upload_file
(
good_sjson
,
self
.
item_module
.
location
)
subs_id
=
_get_subs_id
(
good_sjson
.
name
)
text
=
item
.
get_transcript
(
subs_id
)
expected_text
=
"Hi, welcome to Edx.
\n
Let's start with what is on your screen right now."
self
.
assertEqual
(
text
,
expected_text
)
def
test_not_found_error
(
self
):
self
.
item_module
.
render
(
'student_view'
)
item
=
self
.
item_descriptor
.
xmodule_runtime
.
xmodule_instance
with
self
.
assertRaises
(
NotFoundError
):
item
.
get_transcript
(
'wrong'
)
def
test_value_error
(
self
):
self
.
item_module
.
render
(
'student_view'
)
item
=
self
.
item_descriptor
.
xmodule_runtime
.
xmodule_instance
good_sjson
=
_create_file
(
content
=
"""
bad content
"""
)
_upload_file
(
good_sjson
,
self
.
item_module
.
location
)
subs_id
=
_get_subs_id
(
good_sjson
.
name
)
with
self
.
assertRaises
(
ValueError
):
item
.
get_transcript
(
subs_id
)
def
test_key_error
(
self
):
self
.
item_module
.
render
(
'student_view'
)
item
=
self
.
item_descriptor
.
xmodule_runtime
.
xmodule_instance
good_sjson
=
_create_file
(
content
=
"""
{
"start": [
270,
2720
],
"end": [
2720,
5430
]
}
"""
)
_upload_file
(
good_sjson
,
self
.
item_module
.
location
)
subs_id
=
_get_subs_id
(
good_sjson
.
name
)
with
self
.
assertRaises
(
KeyError
):
item
.
get_transcript
(
subs_id
)
class
TestGetHtmlMethod
(
BaseTestXmodule
):
"""
Make sure that `get_html` works correctly.
"""
CATEGORY
=
"video"
DATA
=
SOURCE_XML
METADATA
=
{}
def
setUp
(
self
):
self
.
setup_course
();
def
test_get_html_track
(
self
):
SOURCE_XML
=
"""
<video show_captions="true"
display_name="A Name"
sub="{sub}" download_track="{download_track}"
start_time="01:00:03" end_time="01:00:10"
>
<source src="example.mp4"/>
<source src="example.webm"/>
{track}
</video>
"""
cases
=
[
{
'download_track'
:
u'true'
,
'track'
:
u'<track src="http://www.example.com/track"/>'
,
'sub'
:
u'a_sub_file.srt.sjson'
,
'expected_track_url'
:
u'http://www.example.com/track'
,
},
{
'download_track'
:
u'true'
,
'track'
:
u''
,
'sub'
:
u'a_sub_file.srt.sjson'
,
'expected_track_url'
:
u'a_sub_file.srt.sjson'
,
},
{
'download_track'
:
u'true'
,
'track'
:
u''
,
'sub'
:
u''
,
'expected_track_url'
:
None
},
{
'download_track'
:
u'false'
,
'track'
:
u'<track src="http://www.example.com/track"/>'
,
'sub'
:
u'a_sub_file.srt.sjson'
,
'expected_track_url'
:
None
,
}
]
expected_context
=
{
'data_dir'
:
getattr
(
self
,
'data_dir'
,
None
),
'caption_asset_path'
:
'/static/subs/'
,
'show_captions'
:
'true'
,
'display_name'
:
u'A Name'
,
'end'
:
3610.0
,
'id'
:
None
,
'sources'
:
{
'main'
:
u'example.mp4'
,
u'mp4'
:
u'example.mp4'
,
u'webm'
:
u'example.webm'
},
'start'
:
3603.0
,
'sub'
:
u'a_sub_file.srt.sjson'
,
'track'
:
''
,
'youtube_streams'
:
'1.00:OEoXaMPEzfM'
,
'autoplay'
:
settings
.
FEATURES
.
get
(
'AUTOPLAY_VIDEOS'
,
True
),
...
...
@@ -112,7 +298,151 @@ class TestVideoNonYouTube(TestVideo):
'yt_test_url'
:
'https://gdata.youtube.com/feeds/api/videos/'
}
for
data
in
cases
:
DATA
=
SOURCE_XML
.
format
(
download_track
=
data
[
'download_track'
],
track
=
data
[
'track'
],
sub
=
data
[
'sub'
],
)
self
.
initialize_module
(
data
=
DATA
)
track_url
=
self
.
item_descriptor
.
xmodule_runtime
.
handler_url
(
self
.
item_module
,
'download_transcript'
)
expected_context
.
update
({
'track'
:
track_url
if
data
[
'expected_track_url'
]
==
u'a_sub_file.srt.sjson'
else
data
[
'expected_track_url'
],
'sub'
:
data
[
'sub'
],
'id'
:
self
.
item_module
.
location
.
html_id
(),
})
context
=
self
.
item_module
.
render
(
'student_view'
)
.
content
self
.
assertEqual
(
context
,
self
.
item_module
.
xmodule_runtime
.
render_template
(
'video.html'
,
expected_context
)
)
class
TestVideoDescriptorInitialization
(
BaseTestXmodule
):
"""
Make sure that module initialization works correctly.
"""
CATEGORY
=
"video"
DATA
=
SOURCE_XML
METADATA
=
{}
def
setUp
(
self
):
self
.
setup_course
();
def
test_track_is_not_empty
(
self
):
metatdata
=
{
'track'
:
'http://example.org/track'
,
}
self
.
initialize_module
(
metadata
=
metatdata
)
fields
=
self
.
item_descriptor
.
editable_metadata_fields
self
.
assertIn
(
'track'
,
fields
)
self
.
assertEqual
(
self
.
item_module
.
track
,
'http://example.org/track'
)
self
.
assertTrue
(
self
.
item_module
.
download_track
)
self
.
assertTrue
(
self
.
item_module
.
track_visible
)
@patch
(
'xmodule.x_module.XModuleDescriptor.editable_metadata_fields'
,
new_callable
=
PropertyMock
)
def
test_download_track_is_explicitly_set
(
self
,
mock_editable_fields
):
mock_editable_fields
.
return_value
=
{
'download_track'
:
{
'default_value'
:
False
,
'explicitly_set'
:
True
,
'display_name'
:
'Transcript Download Allowed'
,
'help'
:
'Show a link beneath the video to allow students to download the transcript.'
,
'type'
:
'Boolean'
,
'value'
:
False
,
'field_name'
:
'download_track'
,
'options'
:
[
{
'display_name'
:
"True"
,
"value"
:
True
},
{
'display_name'
:
"False"
,
"value"
:
False
}
]
},
'track'
:
{
'default_value'
:
''
,
'explicitly_set'
:
False
,
'display_name'
:
'Download Transcript'
,
'help'
:
'The external URL to download the timed transcript track.'
,
'type'
:
'Generic'
,
'value'
:
u'http://example.org/track'
,
'field_name'
:
'track'
,
'options'
:
[]
},
}
metadata
=
{
'track'
:
'http://example.org/track'
,
}
self
.
initialize_module
(
metadata
=
metadata
)
fields
=
self
.
item_descriptor
.
editable_metadata_fields
self
.
assertIn
(
'track'
,
fields
)
self
.
assertEqual
(
self
.
item_module
.
track
,
'http://example.org/track'
)
self
.
assertFalse
(
self
.
item_module
.
download_track
)
self
.
assertTrue
(
self
.
item_module
.
track_visible
)
def
test_track_is_empty
(
self
):
metatdata
=
{
'track'
:
''
,
}
self
.
initialize_module
(
metadata
=
metatdata
)
fields
=
self
.
item_descriptor
.
editable_metadata_fields
self
.
assertNotIn
(
'track'
,
fields
)
self
.
assertEqual
(
self
.
item_module
.
track
,
''
)
self
.
assertFalse
(
self
.
item_module
.
download_track
)
self
.
assertFalse
(
self
.
item_module
.
track_visible
)
def
_clear_assets
(
location
):
store
=
contentstore
()
content_location
=
StaticContent
.
compute_location
(
location
.
org
,
location
.
course
,
location
.
name
)
assets
,
__
=
store
.
get_all_content_for_course
(
content_location
)
for
asset
in
assets
:
asset_location
=
Location
(
asset
[
"_id"
])
id
=
StaticContent
.
get_id_from_location
(
asset_location
)
store
.
delete
(
id
)
def
_get_subs_id
(
filename
):
basename
=
os
.
path
.
splitext
(
os
.
path
.
basename
(
filename
))[
0
]
return
basename
.
replace
(
'subs_'
,
''
)
.
replace
(
'.srt'
,
''
)
def
_create_file
(
content
=
''
):
sjson_file
=
tempfile
.
NamedTemporaryFile
(
prefix
=
"subs_"
,
suffix
=
".srt.sjson"
)
sjson_file
.
content_type
=
'application/json'
sjson_file
.
write
(
textwrap
.
dedent
(
content
))
sjson_file
.
seek
(
0
)
return
sjson_file
def
_upload_file
(
file
,
location
):
filename
=
'subs_{}.srt.sjson'
.
format
(
_get_subs_id
(
file
.
name
))
mime_type
=
file
.
content_type
content_location
=
StaticContent
.
compute_location
(
location
.
org
,
location
.
course
,
filename
)
sc_partial
=
partial
(
StaticContent
,
content_location
,
filename
,
mime_type
)
content
=
sc_partial
(
file
.
read
())
(
thumbnail_content
,
thumbnail_location
)
=
contentstore
()
.
generate_thumbnail
(
content
,
tempfile_path
=
None
)
del_cached_content
(
thumbnail_location
)
if
thumbnail_content
is
not
None
:
content
.
thumbnail_location
=
thumbnail_location
contentstore
()
.
save
(
content
)
del_cached_content
(
content
.
location
)
lms/djangoapps/courseware/tests/test_video_xml.py
View file @
4a36fa89
...
...
@@ -35,7 +35,6 @@ SOURCE_XML = """
>
<source src="example.mp4"/>
<source src="example.webm"/>
<source src="example.ogv"/>
</video>
"""
...
...
@@ -68,12 +67,10 @@ class VideoModuleUnitTest(unittest.TestCase):
def
test_video_get_html
(
self
):
"""Make sure that all parameters extracted correclty from xml"""
module
=
VideoFactory
.
create
()
sources
=
{
'main'
:
'example.mp4'
,
'mp4'
:
'example.mp4'
,
'webm'
:
'example.webm'
,
'ogv'
:
'example.ogv'
}
expected_context
=
{
...
...
@@ -87,7 +84,7 @@ class VideoModuleUnitTest(unittest.TestCase):
'show_captions'
:
'true'
,
'sources'
:
sources
,
'youtube_streams'
:
_create_youtube_string
(
module
),
'track'
:
''
,
'track'
:
None
,
'autoplay'
:
settings
.
FEATURES
.
get
(
'AUTOPLAY_VIDEOS'
,
False
),
'yt_test_timeout'
:
1500
,
'yt_test_url'
:
'https://gdata.youtube.com/feeds/api/videos/'
...
...
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