Commit 11a07ca8 by Mushtaq Ali

Show validation message when no transcript file uploaded for a language in a…

Show validation message when no transcript file uploaded for a language in a video component in studio.
Also, don't show respective transcript language in video language menu when a related transcript is not uploaded for that language.

TNL-5200
parent 47e3c6d4
...@@ -21,6 +21,7 @@ from mock import ANY, Mock, patch ...@@ -21,6 +21,7 @@ from mock import ANY, Mock, patch
import ddt import ddt
from django.conf import settings from django.conf import settings
from django.test.utils import override_settings
from opaque_keys.edx.locations import SlashSeparatedCourseKey from opaque_keys.edx.locations import SlashSeparatedCourseKey
from opaque_keys.edx.keys import CourseKey from opaque_keys.edx.keys import CourseKey
...@@ -28,6 +29,7 @@ from xblock.field_data import DictFieldData ...@@ -28,6 +29,7 @@ from xblock.field_data import DictFieldData
from xblock.fields import ScopeIds from xblock.fields import ScopeIds
from xmodule.tests import get_test_descriptor_system from xmodule.tests import get_test_descriptor_system
from xmodule.validation import StudioValidationMessage
from xmodule.video_module import VideoDescriptor, create_youtube_string from xmodule.video_module import VideoDescriptor, create_youtube_string
from xmodule.video_module.transcripts_utils import download_youtube_subs, save_to_store from xmodule.video_module.transcripts_utils import download_youtube_subs, save_to_store
from . import LogicTest from . import LogicTest
...@@ -77,6 +79,12 @@ YOUTUBE_SUBTITLES = ( ...@@ -77,6 +79,12 @@ YOUTUBE_SUBTITLES = (
" that now. The tutorial will continue in the next video." " that now. The tutorial will continue in the next video."
) )
ALL_LANGUAGES = (
[u"en", u"English"],
[u"eo", u"Esperanto"],
[u"ur", u"Urdu"]
)
def instantiate_descriptor(**field_data): def instantiate_descriptor(**field_data):
""" """
...@@ -780,6 +788,7 @@ class VideoExportTestCase(VideoDescriptorTestBase): ...@@ -780,6 +788,7 @@ class VideoExportTestCase(VideoDescriptorTestBase):
self.assertEqual(xml.get('display_name'), u'\u8fd9\u662f\u6587') self.assertEqual(xml.get('display_name'), u'\u8fd9\u662f\u6587')
@ddt.ddt
class VideoDescriptorIndexingTestCase(unittest.TestCase): class VideoDescriptorIndexingTestCase(unittest.TestCase):
""" """
Make sure that VideoDescriptor can format data for indexing as expected. Make sure that VideoDescriptor can format data for indexing as expected.
...@@ -993,3 +1002,74 @@ class VideoDescriptorIndexingTestCase(unittest.TestCase): ...@@ -993,3 +1002,74 @@ class VideoDescriptorIndexingTestCase(unittest.TestCase):
descriptor = instantiate_descriptor(data=None) descriptor = instantiate_descriptor(data=None)
translations = descriptor.available_translations(descriptor.get_transcripts_info(), verify_assets=False) translations = descriptor.available_translations(descriptor.get_transcripts_info(), verify_assets=False)
self.assertEqual(translations, ['en']) self.assertEqual(translations, ['en'])
@override_settings(ALL_LANGUAGES=ALL_LANGUAGES)
def test_video_with_language_do_not_have_transcripts_translation(self):
"""
Test translation retrieval of a video module with
a language having no transcripts uploaded by a user.
"""
xml_data_transcripts = '''
<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"
download_video="false"
end_time="00:01:00">
<source src="http://www.example.com/source.mp4"/>
<track src="http://www.example.com/track"/>
<handout src="http://www.example.com/handout"/>
<transcript language="ur" src="" />
</video>
'''
descriptor = instantiate_descriptor(data=xml_data_transcripts)
translations = descriptor.available_translations(descriptor.get_transcripts_info(), verify_assets=False)
self.assertNotEqual(translations, ['ur'])
def assert_validation_message(self, validation, expected_msg):
"""
Asserts that the validation message has all expected content.
Args:
validation (StudioValidation): A validation object.
expected_msg (string): An expected validation message.
"""
self.assertFalse(validation.empty) # Validation contains some warning/message
self.assertTrue(validation.summary)
self.assertEqual(StudioValidationMessage.WARNING, validation.summary.type)
self.assertIn(expected_msg, validation.summary.text)
@ddt.data(
(
'<transcript language="ur" src="" />',
'There is no transcript file associated with the Urdu language.'
),
(
'<transcript language="eo" src="" /><transcript language="ur" src="" />',
'There are no transcript files associated with the Esperanto, Urdu languages.'
),
)
@ddt.unpack
@override_settings(ALL_LANGUAGES=ALL_LANGUAGES)
def test_no_transcript_validation_message(self, xml_transcripts, expected_validation_msg):
"""
Test the validation message when no associated transcript file uploaded.
"""
xml_data_transcripts = '''
<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"
download_video="false"
end_time="00:01:00">
<source src="http://www.example.com/source.mp4"/>
<track src="http://www.example.com/track"/>
<handout src="http://www.example.com/handout"/>
{xml_transcripts}
</video>
'''.format(xml_transcripts=xml_transcripts)
descriptor = instantiate_descriptor(data=xml_data_transcripts)
validation = descriptor.validate()
self.assert_validation_message(validation, expected_validation_msg)
...@@ -383,9 +383,7 @@ def manage_video_subtitles_save(item, user, old_metadata=None, generate_translat ...@@ -383,9 +383,7 @@ def manage_video_subtitles_save(item, user, old_metadata=None, generate_translat
lang, lang,
) )
except TranscriptException as ex: except TranscriptException as ex:
# remove key from transcripts because proper srt file does not exist in assets. pass
item.transcripts.pop(lang)
reraised_message += ' ' + ex.message
if reraised_message: if reraised_message:
item.save_with_metadata(user) item.save_with_metadata(user)
raise TranscriptException(reraised_message) raise TranscriptException(reraised_message)
...@@ -531,6 +529,9 @@ class Transcript(object): ...@@ -531,6 +529,9 @@ class Transcript(object):
""" """
Return asset location. `location` is module location. Return asset location. `location` is module location.
""" """
# If user transcript filename is empty, raise `TranscriptException` to avoid `InvalidKeyError`.
if not filename:
raise TranscriptException("Transcript not uploaded yet")
return StaticContent.compute_location(location.course_key, filename) return StaticContent.compute_location(location.course_key, filename)
@staticmethod @staticmethod
...@@ -670,12 +671,17 @@ class VideoTranscriptsMixin(object): ...@@ -670,12 +671,17 @@ class VideoTranscriptsMixin(object):
""" """
if is_bumper: if is_bumper:
transcripts = copy.deepcopy(get_bumper_settings(self).get('transcripts', {})) transcripts = copy.deepcopy(get_bumper_settings(self).get('transcripts', {}))
return { sub = transcripts.pop("en", "")
"sub": transcripts.pop("en", ""),
"transcripts": transcripts,
}
else: else:
return { transcripts = self.transcripts
"sub": self.sub, sub = self.sub
"transcripts": self.transcripts,
} # Only attach transcripts that are not empty.
transcripts = {
language_code: transcript_file
for language_code, transcript_file in transcripts.items() if transcript_file != ''
}
return {
"sub": sub,
"transcripts": transcripts,
}
...@@ -36,6 +36,7 @@ from xmodule.raw_module import EmptyDataRawDescriptor ...@@ -36,6 +36,7 @@ from xmodule.raw_module import EmptyDataRawDescriptor
from xmodule.xml_module import is_pointer_tag, name_to_pathname, deserialize_field from xmodule.xml_module import is_pointer_tag, name_to_pathname, deserialize_field
from xmodule.exceptions import NotFoundError from xmodule.exceptions import NotFoundError
from xmodule.contentstore.content import StaticContent from xmodule.contentstore.content import StaticContent
from xmodule.validation import StudioValidationMessage, StudioValidation
from .transcripts_utils import VideoTranscriptsMixin, Transcript, get_html5_ids from .transcripts_utils import VideoTranscriptsMixin, Transcript, get_html5_ids
from .video_utils import create_youtube_string, get_poster, rewrite_video_url, format_xml_exception_message from .video_utils import create_youtube_string, get_poster, rewrite_video_url, format_xml_exception_message
...@@ -152,6 +153,12 @@ class VideoModule(VideoFields, VideoTranscriptsMixin, VideoStudentViewHandlers, ...@@ -152,6 +153,12 @@ class VideoModule(VideoFields, VideoTranscriptsMixin, VideoStudentViewHandlers,
]} ]}
js_module_name = "Video" js_module_name = "Video"
def validate(self):
"""
Validates the state of this Video Module Instance.
"""
return self.descriptor.validate()
def get_transcripts_for_student(self, transcripts): def get_transcripts_for_student(self, transcripts):
"""Return transcript information necessary for rendering the XModule student view. """Return transcript information necessary for rendering the XModule student view.
This is more or less a direct extraction from `get_html`. This is more or less a direct extraction from `get_html`.
...@@ -427,6 +434,35 @@ class VideoDescriptor(VideoFields, VideoTranscriptsMixin, VideoStudioViewHandler ...@@ -427,6 +434,35 @@ class VideoDescriptor(VideoFields, VideoTranscriptsMixin, VideoStudioViewHandler
if not self.fields['download_track'].is_set_on(self) and self.track: if not self.fields['download_track'].is_set_on(self) and self.track:
self.download_track = True self.download_track = True
def validate(self):
"""
Validates the state of this video Module Instance. This
is the override of the general XBlock method, and it will also ask
its superclass to validate.
"""
validation = super(VideoDescriptor, self).validate()
if not isinstance(validation, StudioValidation):
validation = StudioValidation.copy(validation)
no_transcript_lang = []
for lang_code, transcript in self.transcripts.items():
if not transcript:
no_transcript_lang.append([label for code, label in settings.ALL_LANGUAGES if code == lang_code][0])
if no_transcript_lang:
ungettext = self.runtime.service(self, "i18n").ungettext
validation.set_summary(
StudioValidationMessage(
StudioValidationMessage.WARNING,
ungettext(
'There is no transcript file associated with the {lang} language.',
'There are no transcript files associated with the {lang} languages.',
len(no_transcript_lang)
).format(lang=', '.join(no_transcript_lang))
)
)
return validation
def editor_saved(self, user, old_metadata, old_content): def editor_saved(self, user, old_metadata, old_content):
""" """
Used to update video values during `self`:save method from CMS. Used to update video values during `self`:save method from CMS.
......
...@@ -126,6 +126,27 @@ class VideoEditorTest(CMSVideoBaseTest): ...@@ -126,6 +126,27 @@ class VideoEditorTest(CMSVideoBaseTest):
self.assertIn(unicode_text, self.video.captions_text) self.assertIn(unicode_text, self.video.captions_text)
self.assertEqual(self.video.caption_languages.keys(), ['zh', 'uk']) self.assertEqual(self.video.caption_languages.keys(), ['zh', 'uk'])
def test_save_language_upload_no_transcript(self):
"""
Scenario: Transcript language is not shown in language menu if no transcript file is uploaded
Given I have created a Video component
And I edit the component
And I open tab "Advanced"
And I add a language "uk" but do not upload an .srt file
And I save changes
When I view the video language menu
Then I am not able to see the language "uk" translation language
"""
self._create_video_component()
self.edit_component()
self.open_advanced_tab()
language_code = 'uk'
self.video.click_button('translation_add')
translations_count = self.video.translations_count()
self.video.select_translation_language(language_code, translations_count - 1)
self.save_unit_settings()
self.assertNotIn(language_code, self.video.caption_languages.keys())
def test_upload_large_transcript(self): def test_upload_large_transcript(self):
""" """
Scenario: User can upload transcript file with > 1mb size Scenario: User can upload transcript file with > 1mb size
......
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