From 4a36fa892bcc5432a569930a5b0116eb65fc3832 Mon Sep 17 00:00:00 2001
From: polesye <s2pak.anton@gmail.com>
Date: Thu, 26 Dec 2013 12:41:12 +0200
Subject: [PATCH] BLD-368: Turn "download transcript" into a dropdown.

---
 CHANGELOG.rst                                                             |   3 +++
 cms/djangoapps/contentstore/features/component_settings_editor_helpers.py |   2 +-
 cms/djangoapps/contentstore/features/video-editor.py                      |   2 +-
 cms/static/js/views/metadata.js                                           |  13 +++++++++++++
 cms/static/sass/views/_unit.scss                                          |   6 ++++++
 common/lib/xmodule/xmodule/tests/test_video.py                            |  22 +++++++++++++++++-----
 common/lib/xmodule/xmodule/video_module.py                                | 103 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
 lms/djangoapps/courseware/tests/__init__.py                               |  52 ++++++++++++++++++++++++++++++----------------------
 lms/djangoapps/courseware/tests/test_video_mongo.py                       | 346 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
 lms/djangoapps/courseware/tests/test_video_xml.py                         |   5 +----
 10 files changed, 509 insertions(+), 45 deletions(-)

diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 6b0af2d..a7a77c1 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -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.
diff --git a/cms/djangoapps/contentstore/features/component_settings_editor_helpers.py b/cms/djangoapps/contentstore/features/component_settings_editor_helpers.py
index 932c21b..d3c0beb 100644
--- a/cms/djangoapps/contentstore/features/component_settings_editor_helpers.py
+++ b/cms/djangoapps/contentstore/features/component_settings_editor_helpers.py
@@ -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
diff --git a/cms/djangoapps/contentstore/features/video-editor.py b/cms/djangoapps/contentstore/features/video-editor.py
index 6f78014..5b4dffc 100644
--- a/cms/djangoapps/contentstore/features/video-editor.py
+++ b/cms/djangoapps/contentstore/features/video-editor.py
@@ -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],
diff --git a/cms/static/js/views/metadata.js b/cms/static/js/views/metadata.js
index 2f07ab1..9864611 100644
--- a/cms/static/js/views/metadata.js
+++ b/cms/static/js/views/metadata.js
@@ -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();
         },
diff --git a/cms/static/sass/views/_unit.scss b/cms/static/sass/views/_unit.scss
index 663fa31..d240fd7 100644
--- a/cms/static/sass/views/_unit.scss
+++ b/cms/static/sass/views/_unit.scss
@@ -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%;
diff --git a/common/lib/xmodule/xmodule/tests/test_video.py b/common/lib/xmodule/xmodule/tests/test_video.py
index 78f5205..116aad7 100644
--- a/common/lib/xmodule/xmodule/tests/test_video.py
+++ b/common/lib/xmodule/xmodule/tests/test_video.py
@@ -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="&quot;http://download_video&quot;"
                 sub="&quot;html5_subtitles&quot;"
                 track="&quot;http://download_track&quot;"
+                download_track="true"
                 youtube_id_0_75="&quot;OEoXaMPEzf65&quot;"
                 youtube_id_1_25="&quot;OEoXaMPEzf125&quot;"
                 youtube_id_1_5="&quot;OEoXaMPEzf15&quot;"
@@ -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"/>
diff --git a/common/lib/xmodule/xmodule/video_module.py b/common/lib/xmodule/xmodule/video_module.py
index 2a27c03..9dd7a0c 100644
--- a/common/lib/xmodule/xmodule/video_module.py
+++ b/common/lib/xmodule/xmodule/video_module.py
@@ -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):
diff --git a/lms/djangoapps/courseware/tests/__init__.py b/lms/djangoapps/courseware/tests/__init__.py
index fcca925..fcd9251 100644
--- a/lms/djangoapps/courseware/tests/__init__.py
+++ b/lms/djangoapps/courseware/tests/__init__.py
@@ -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(
+        self.section = 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(
diff --git a/lms/djangoapps/courseware/tests/test_video_mongo.py b/lms/djangoapps/courseware/tests/test_video_mongo.py
index e3c4ea5..386cdc8 100644
--- a/lms/djangoapps/courseware/tests/test_video_mongo.py
+++ b/lms/djangoapps/courseware/tests/test_video_mongo.py
@@ -1,17 +1,27 @@
 # -*- 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,7 +119,7 @@ class TestVideoNonYouTube(TestVideo):
             'sources': sources,
             'start': 3603.0,
             'sub': u'a_sub_file.srt.sjson',
-            'track': '',
+            'track': None,
             'youtube_streams': '1.00:OEoXaMPEzfM',
             'autoplay': settings.FEATURES.get('AUTOPLAY_VIDEOS', True),
             'yt_test_timeout': 1500,
@@ -116,3 +130,319 @@ class TestVideoNonYouTube(TestVideo):
             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&#39;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.\nLet'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),
+            'yt_test_timeout': 1500,
+            '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)
diff --git a/lms/djangoapps/courseware/tests/test_video_xml.py b/lms/djangoapps/courseware/tests/test_video_xml.py
index 87e430c..2cfa3e1 100644
--- a/lms/djangoapps/courseware/tests/test_video_xml.py
+++ b/lms/djangoapps/courseware/tests/test_video_xml.py
@@ -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/'
--
libgit2 0.26.0