Commit 7aac4a60 by Qubad786

Download transcript on video upload page - EDUCATOR-1853

parent 333842a3
...@@ -169,3 +169,85 @@ class TranscriptCredentialsValidationTest(TestCase): ...@@ -169,3 +169,85 @@ class TranscriptCredentialsValidationTest(TestCase):
# Assert the results. # Assert the results.
self.assertEqual(error_message, expected_error_message) self.assertEqual(error_message, expected_error_message)
self.assertDictEqual(validated_credentials, expected_validated_credentials) self.assertDictEqual(validated_credentials, expected_validated_credentials)
@ddt.ddt
@patch(
'openedx.core.djangoapps.video_config.models.VideoTranscriptEnabledFlag.feature_enabled',
Mock(return_value=True)
)
class TranscriptDownloadTest(CourseTestCase):
"""
Tests for transcript download handler.
"""
VIEW_NAME = 'transcript_download_handler'
def get_url_for_course_key(self, course_id):
return reverse_course_url(self.VIEW_NAME, course_id)
def test_302_with_anonymous_user(self):
"""
Verify that redirection happens in case of unauthorized request.
"""
self.client.logout()
transcript_download_url = self.get_url_for_course_key(self.course.id)
response = self.client.get(transcript_download_url, content_type='application/json')
self.assertEqual(response.status_code, 302)
def test_405_with_not_allowed_request_method(self):
"""
Verify that 405 is returned in case of not-allowed request methods.
Allowed request methods include GET.
"""
transcript_download_url = self.get_url_for_course_key(self.course.id)
response = self.client.post(transcript_download_url, content_type='application/json')
self.assertEqual(response.status_code, 405)
def test_404_with_feature_disabled(self):
"""
Verify that 404 is returned if the corresponding feature is disabled.
"""
transcript_download_url = self.get_url_for_course_key(self.course.id)
with patch('openedx.core.djangoapps.video_config.models.VideoTranscriptEnabledFlag.feature_enabled') as feature:
feature.return_value = False
response = self.client.get(transcript_download_url, content_type='application/json')
self.assertEqual(response.status_code, 404)
@patch('contentstore.views.transcript_settings.get_video_transcript_data')
def test_transcript_download_handler(self, mock_get_video_transcript_data):
"""
Tests that transcript download handler works as expected.
"""
transcript_download_url = self.get_url_for_course_key(self.course.id)
mock_get_video_transcript_data.return_value = {
'content': json.dumps({
"start": [10],
"end": [100],
"text": ["Hi, welcome to Edx."],
}),
'file_name': 'edx.sjson'
}
# Make request to transcript download handler
response = self.client.get(
transcript_download_url,
data={
'edx_video_id': '123',
'language_code': 'en'
},
content_type='application/json'
)
# Expected response
expected_content = u'0\n00:00:00,010 --> 00:00:00,100\nHi, welcome to Edx.\n\n'
expected_headers = {
'Content-Disposition': 'attachment; filename="edx.srt"',
'Content-Language': u'en',
'Content-Type': 'application/x-subrip; charset=utf-8'
}
# Assert the actual response
self.assertEqual(response.status_code, 200)
self.assertEqual(response.content, expected_content)
for attribute, value in expected_headers.iteritems():
self.assertEqual(response.get(attribute), value)
""" """
Views related to the transcript preferences feature Views related to the transcript preferences feature
""" """
import os
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.http import HttpResponseNotFound from django.http import HttpResponseNotFound, HttpResponse
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.views.decorators.http import require_POST from django.views.decorators.http import require_POST, require_GET
from edxval.api import ( from edxval.api import (
get_3rd_party_transcription_plans, get_3rd_party_transcription_plans,
get_video_transcript_data,
update_transcript_credentials_state_for_org, update_transcript_credentials_state_for_org,
) )
from opaque_keys.edx.keys import CourseKey from opaque_keys.edx.keys import CourseKey
...@@ -16,8 +19,9 @@ from openedx.core.djangoapps.video_pipeline.api import update_3rd_party_transcri ...@@ -16,8 +19,9 @@ from openedx.core.djangoapps.video_pipeline.api import update_3rd_party_transcri
from util.json_request import JsonResponse, expect_json from util.json_request import JsonResponse, expect_json
from contentstore.views.videos import TranscriptProvider from contentstore.views.videos import TranscriptProvider
from xmodule.video_module.transcripts_utils import Transcript
__all__ = ['transcript_credentials_handler'] __all__ = ['transcript_credentials_handler', 'transcript_download_handler']
class TranscriptionProviderErrorType: class TranscriptionProviderErrorType:
...@@ -108,3 +112,46 @@ def transcript_credentials_handler(request, course_key_string): ...@@ -108,3 +112,46 @@ def transcript_credentials_handler(request, course_key_string):
response = JsonResponse({'error': error_message}, status=400) response = JsonResponse({'error': error_message}, status=400)
return response return response
@expect_json
@login_required
@require_GET
def transcript_download_handler(request, course_key_string):
"""
JSON view handler to download a transcript.
Arguments:
request: WSGI request object
course_key_string: course key
filename: Name of the to be created transcript file
Returns:
- A 200 response with SRT transcript file attached.
- A 400 if there is a validation error.
- A 404 if there is no such transcript or feature flag is disabled.
"""
course_key = CourseKey.from_string(course_key_string)
if not VideoTranscriptEnabledFlag.feature_enabled(course_key):
return HttpResponseNotFound()
edx_video_id = request.GET.get('edx_video_id')
if not edx_video_id:
return JsonResponse({'error': 'edx_video_id is required.'}, status=400)
language_code = request.GET.get('language_code')
if not language_code:
return JsonResponse({'error': 'language code is required.'}, status=400)
transcript = get_video_transcript_data(video_ids=[edx_video_id], language_code=language_code)
if transcript:
basename, __ = os.path.splitext(transcript['file_name'])
transcript_filename = '{base_name}.srt'.format(base_name=basename.encode('utf8'))
transcript_content = Transcript.convert(transcript['content'], input_format='sjson', output_format='srt')
# Construct an HTTP response
response = HttpResponse(transcript_content, content_type=Transcript.mime_types['srt'])
response['Content-Disposition'] = 'attachment; filename="{filename}"'.format(filename=transcript_filename)
else:
response = HttpResponseNotFound()
return response
...@@ -642,7 +642,11 @@ def videos_index_html(course): ...@@ -642,7 +642,11 @@ def videos_index_html(course):
'transcript_credentials_handler', 'transcript_credentials_handler',
unicode(course.id) unicode(course.id)
), ),
'transcription_plans': get_3rd_party_transcription_plans(), 'transcript_download_handler_url': reverse_course_url(
'transcript_download_handler',
unicode(course.id)
),
'transcription_plans': get_3rd_party_transcription_plans()
} }
context['active_transcript_preferences'] = get_transcript_preferences(unicode(course.id)) context['active_transcript_preferences'] = get_transcript_preferences(unicode(course.id))
# Cached state for transcript providers' credentials (org-specific) # Cached state for transcript providers' credentials (org-specific)
......
...@@ -53,6 +53,7 @@ define([ ...@@ -53,6 +53,7 @@ define([
collection: updatedCollection, collection: updatedCollection,
encodingsDownloadUrl: encodingsDownloadUrl, encodingsDownloadUrl: encodingsDownloadUrl,
videoImageSettings: videoImageSettings, videoImageSettings: videoImageSettings,
videoTranscriptSettings: videoTranscriptSettings,
transcriptAvailableLanguages: transcriptAvailableLanguages, transcriptAvailableLanguages: transcriptAvailableLanguages,
videoSupportedFileFormats: videoSupportedFileFormats, videoSupportedFileFormats: videoSupportedFileFormats,
isVideoTranscriptEnabled: isVideoTranscriptEnabled isVideoTranscriptEnabled: isVideoTranscriptEnabled
...@@ -68,6 +69,7 @@ define([ ...@@ -68,6 +69,7 @@ define([
collection: new Backbone.Collection(previousUploads), collection: new Backbone.Collection(previousUploads),
encodingsDownloadUrl: encodingsDownloadUrl, encodingsDownloadUrl: encodingsDownloadUrl,
videoImageSettings: videoImageSettings, videoImageSettings: videoImageSettings,
videoTranscriptSettings: videoTranscriptSettings,
transcriptAvailableLanguages: transcriptAvailableLanguages, transcriptAvailableLanguages: transcriptAvailableLanguages,
videoSupportedFileFormats: videoSupportedFileFormats, videoSupportedFileFormats: videoSupportedFileFormats,
isVideoTranscriptEnabled: isVideoTranscriptEnabled isVideoTranscriptEnabled: isVideoTranscriptEnabled
......
...@@ -36,7 +36,8 @@ define( ...@@ -36,7 +36,8 @@ define(
edxVideoID: this.model.get('edx_video_id'), edxVideoID: this.model.get('edx_video_id'),
clientVideoID: this.model.get('client_video_id'), clientVideoID: this.model.get('client_video_id'),
transcriptAvailableLanguages: options.transcriptAvailableLanguages, transcriptAvailableLanguages: options.transcriptAvailableLanguages,
videoSupportedFileFormats: options.videoSupportedFileFormats videoSupportedFileFormats: options.videoSupportedFileFormats,
videoTranscriptSettings: options.videoTranscriptSettings
}); });
} }
}, },
......
...@@ -17,6 +17,7 @@ define( ...@@ -17,6 +17,7 @@ define(
defaultVideoImageURL: options.defaultVideoImageURL, defaultVideoImageURL: options.defaultVideoImageURL,
videoHandlerUrl: options.videoHandlerUrl, videoHandlerUrl: options.videoHandlerUrl,
videoImageSettings: options.videoImageSettings, videoImageSettings: options.videoImageSettings,
videoTranscriptSettings: options.videoTranscriptSettings,
model: model, model: model,
transcriptAvailableLanguages: options.transcriptAvailableLanguages, transcriptAvailableLanguages: options.transcriptAvailableLanguages,
videoSupportedFileFormats: options.videoSupportedFileFormats, videoSupportedFileFormats: options.videoSupportedFileFormats,
......
...@@ -20,6 +20,7 @@ define( ...@@ -20,6 +20,7 @@ define(
this.clientVideoID = options.clientVideoID; this.clientVideoID = options.clientVideoID;
this.transcriptAvailableLanguages = options.transcriptAvailableLanguages; this.transcriptAvailableLanguages = options.transcriptAvailableLanguages;
this.videoSupportedFileFormats = options.videoSupportedFileFormats; this.videoSupportedFileFormats = options.videoSupportedFileFormats;
this.videoTranscriptSettings = options.videoTranscriptSettings;
this.template = HtmlUtils.template(videoTranscriptsTemplate); this.template = HtmlUtils.template(videoTranscriptsTemplate);
}, },
...@@ -90,6 +91,7 @@ define( ...@@ -90,6 +91,7 @@ define(
transcriptAvailableLanguages: this.sortByValue(this.transcriptAvailableLanguages), transcriptAvailableLanguages: this.sortByValue(this.transcriptAvailableLanguages),
edxVideoID: this.edxVideoID, edxVideoID: this.edxVideoID,
transcriptClientTitle: this.getTranscriptClientTitle(), transcriptClientTitle: this.getTranscriptClientTitle(),
transcriptDownloadHandlerUrl: this.videoTranscriptSettings.transcript_download_handler_url,
transcriptDownloadFileFormat: TRANSCRIPT_DOWNLOAD_FILE_FORMAT transcriptDownloadFileFormat: TRANSCRIPT_DOWNLOAD_FILE_FORMAT
}) })
); );
......
...@@ -21,7 +21,17 @@ ...@@ -21,7 +21,17 @@
<% }) %> <% }) %>
</select> </select>
<div class='transcript-actions'> <div class='transcript-actions'>
<button class="button-link download-transcript-button" data-edx-video-id="<%- edxVideoID %>" data-language-code="<%- transcriptLanguageCode %>"> <a
class="button-link download-transcript-button"
href="<%- StringUtils.interpolate(
'{transcriptDownloadHandlerUrl}?edx_video_id={edxVideoID}&language_code={transcriptLanguageCode}',
{
transcriptDownloadHandlerUrl: transcriptDownloadHandlerUrl,
edxVideoID: edxVideoID,
transcriptLanguageCode: transcriptLanguageCode
}
) %>"
>
<%- gettext('Download') %> <%- gettext('Download') %>
</button> </button>
<span class='transcript-actions-separator'> | </span> <span class='transcript-actions-separator'> | </span>
......
...@@ -142,6 +142,8 @@ urlpatterns = [ ...@@ -142,6 +142,8 @@ urlpatterns = [
contentstore.views.transcript_preferences_handler, name='transcript_preferences_handler'), contentstore.views.transcript_preferences_handler, name='transcript_preferences_handler'),
url(r'^transcript_credentials/{}$'.format(settings.COURSE_KEY_PATTERN), url(r'^transcript_credentials/{}$'.format(settings.COURSE_KEY_PATTERN),
contentstore.views.transcript_credentials_handler, name='transcript_credentials_handler'), contentstore.views.transcript_credentials_handler, name='transcript_credentials_handler'),
url(r'^transcript_download/{}$'.format(settings.COURSE_KEY_PATTERN),
contentstore.views.transcript_download_handler, name='transcript_download_handler'),
url(r'^video_encodings_download/{}$'.format(settings.COURSE_KEY_PATTERN), url(r'^video_encodings_download/{}$'.format(settings.COURSE_KEY_PATTERN),
contentstore.views.video_encodings_download, name='video_encodings_download'), contentstore.views.video_encodings_download, name='video_encodings_download'),
url(r'^group_configurations/{}$'.format(settings.COURSE_KEY_PATTERN), url(r'^group_configurations/{}$'.format(settings.COURSE_KEY_PATTERN),
......
...@@ -101,6 +101,7 @@ git+https://github.com/edx/xblock-utils.git@v1.0.5#egg=xblock-utils==1.0.5 ...@@ -101,6 +101,7 @@ git+https://github.com/edx/xblock-utils.git@v1.0.5#egg=xblock-utils==1.0.5
git+https://github.com/edx/edx-user-state-client.git@1.0.2#egg=edx-user-state-client==1.0.2 git+https://github.com/edx/edx-user-state-client.git@1.0.2#egg=edx-user-state-client==1.0.2
git+https://github.com/edx/xblock-lti-consumer.git@v1.1.6#egg=lti_consumer-xblock==1.1.6 git+https://github.com/edx/xblock-lti-consumer.git@v1.1.6#egg=lti_consumer-xblock==1.1.6
git+https://github.com/edx/edx-proctoring.git@1.3.1#egg=edx-proctoring==1.3.1 git+https://github.com/edx/edx-proctoring.git@1.3.1#egg=edx-proctoring==1.3.1
-e git+https://github.com/edx/edx-val.git@mrehan/download-transcripts-on-upload-page#egg=edxval
# This is here because all of the other XBlocks are located here. However, it is published to PyPI and will be installed that way # This is here because all of the other XBlocks are located here. However, it is published to PyPI and will be installed that way
xblock-review==1.1.2 xblock-review==1.1.2
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment