Unverified Commit 727e643d by M. Rehan Committed by GitHub

Merge pull request #117 from edx/mrehan/transcript-foreign-key

Make Video an explicit foreign key in VideoTranscript model
parents 7d5c04df eee4c2f0
...@@ -69,7 +69,7 @@ class CourseVideoAdmin(admin.ModelAdmin): ...@@ -69,7 +69,7 @@ class CourseVideoAdmin(admin.ModelAdmin):
class VideoTranscriptAdmin(admin.ModelAdmin): class VideoTranscriptAdmin(admin.ModelAdmin):
list_display = ('video_id', 'language_code', 'provider', 'file_format') list_display = ('video', 'language_code', 'provider', 'file_format')
model = VideoTranscript model = VideoTranscript
......
...@@ -192,7 +192,7 @@ def is_transcript_available(video_id, language_code=None): ...@@ -192,7 +192,7 @@ def is_transcript_available(video_id, language_code=None):
video_id: it can be an edx_video_id or an external_id extracted from external sources in a video component. video_id: it can be an edx_video_id or an external_id extracted from external sources in a video component.
language_code: it will the language code of the requested transcript. language_code: it will the language code of the requested transcript.
""" """
filter_attrs = {'video_id': video_id} filter_attrs = {'video__edx_video_id': video_id}
if language_code: if language_code:
filter_attrs['language_code'] = language_code filter_attrs['language_code'] = language_code
...@@ -200,22 +200,6 @@ def is_transcript_available(video_id, language_code=None): ...@@ -200,22 +200,6 @@ def is_transcript_available(video_id, language_code=None):
return transcript_set.exists() return transcript_set.exists()
def get_video_transcripts(video_id):
"""
Get a video's transcripts
Arguments:
video_id: it can be an edx_video_id or an external_id extracted from external sources in a video component.
"""
transcripts_set = VideoTranscript.objects.filter(video_id=video_id)
transcripts = []
if transcripts_set.exists():
transcripts = TranscriptSerializer(transcripts_set, many=True).data
return transcripts
def get_video_transcript(video_id, language_code): def get_video_transcript(video_id, language_code):
""" """
Get video transcript info Get video transcript info
...@@ -245,7 +229,7 @@ def get_video_transcript_data(video_ids, language_code): ...@@ -245,7 +229,7 @@ def get_video_transcript_data(video_ids, language_code):
transcript_data = None transcript_data = None
for video_id in video_ids: for video_id in video_ids:
try: try:
video_transcript = VideoTranscript.objects.get(video_id=video_id, language_code=language_code) video_transcript = VideoTranscript.objects.get(video__edx_video_id=video_id, language_code=language_code)
transcript_data = dict( transcript_data = dict(
file_name=video_transcript.filename, file_name=video_transcript.filename,
content=video_transcript.transcript.file.read() content=video_transcript.transcript.file.read()
...@@ -276,7 +260,7 @@ def get_available_transcript_languages(video_ids): ...@@ -276,7 +260,7 @@ def get_available_transcript_languages(video_ids):
A list containing unique transcript language codes for the video ids. A list containing unique transcript language codes for the video ids.
""" """
available_languages = VideoTranscript.objects.filter( available_languages = VideoTranscript.objects.filter(
video_id__in=video_ids video__edx_video_id__in=video_ids
).values_list( ).values_list(
'language_code', flat=True 'language_code', flat=True
) )
...@@ -324,7 +308,12 @@ def create_or_update_video_transcript(video_id, language_code, metadata, file_da ...@@ -324,7 +308,12 @@ def create_or_update_video_transcript(video_id, language_code, metadata, file_da
if provider and provider not in dict(TranscriptProviderType.CHOICES).keys(): if provider and provider not in dict(TranscriptProviderType.CHOICES).keys():
raise InvalidTranscriptProvider('{} transcript provider is not supported'.format(provider)) raise InvalidTranscriptProvider('{} transcript provider is not supported'.format(provider))
video_transcript, __ = VideoTranscript.create_or_update(video_id, language_code, metadata, file_data) try:
# Video should be present in edxval in order to attach transcripts to it.
video = Video.objects.get(edx_video_id=video_id)
video_transcript, __ = VideoTranscript.create_or_update(video, language_code, metadata, file_data)
except Video.DoesNotExist:
return None
return video_transcript.url() return video_transcript.url()
...@@ -338,7 +327,7 @@ def delete_video_transcript(video_id, language_code): ...@@ -338,7 +327,7 @@ def delete_video_transcript(video_id, language_code):
language_code: language code of a video transcript language_code: language code of a video transcript
""" """
try: try:
video_transcript = VideoTranscript.objects.get(video_id=video_id, language_code=language_code) video_transcript = VideoTranscript.objects.get(video__edx_video_id=video_id, language_code=language_code)
# delete the actual transcript file from storage # delete the actual transcript file from storage
video_transcript.transcript.delete() video_transcript.transcript.delete()
# delete the record from db # delete the record from db
...@@ -775,10 +764,9 @@ def export_to_xml(video_ids, course_id=None, external=False): ...@@ -775,10 +764,9 @@ def export_to_xml(video_ids, course_id=None, external=False):
Raises: Raises:
ValVideoNotFoundError: if the video does not exist ValVideoNotFoundError: if the video does not exist
""" """
# val does not store external videos, so construct transcripts information only. # TODO: This will be removed as a part of EDUCATOR-1789
if external: if external:
video_el = Element('video_asset') return Element('video_asset')
return create_transcripts_xml(video_ids, video_el)
# for an internal video, first video id must be edx_video_id # for an internal video, first video id must be edx_video_id
video_id = video_ids[0] video_id = video_ids[0]
...@@ -824,7 +812,7 @@ def create_transcripts_xml(video_ids, video_el): ...@@ -824,7 +812,7 @@ def create_transcripts_xml(video_ids, video_el):
Returns: Returns:
lxml Element object with transcripts information lxml Element object with transcripts information
""" """
video_transcripts = VideoTranscript.objects.filter(video_id__in=video_ids) video_transcripts = VideoTranscript.objects.filter(video__edx_video_id__in=video_ids).order_by('language_code')
# create transcripts node only when we have transcripts for a video # create transcripts node only when we have transcripts for a video
if video_transcripts.exists(): if video_transcripts.exists():
transcripts_el = SubElement(video_el, 'transcripts') transcripts_el = SubElement(video_el, 'transcripts')
...@@ -836,7 +824,7 @@ def create_transcripts_xml(video_ids, video_el): ...@@ -836,7 +824,7 @@ def create_transcripts_xml(video_ids, video_el):
transcripts_el, transcripts_el,
'transcript', 'transcript',
{ {
'video_id': video_transcript.video_id, 'video_id': video_transcript.video.edx_video_id,
'file_name': video_transcript.transcript.name, 'file_name': video_transcript.transcript.name,
'language_code': video_transcript.language_code, 'language_code': video_transcript.language_code,
'file_format': video_transcript.file_format, 'file_format': video_transcript.file_format,
...@@ -866,9 +854,9 @@ def import_from_xml(xml, edx_video_id, course_id=None): ...@@ -866,9 +854,9 @@ def import_from_xml(xml, edx_video_id, course_id=None):
if xml.tag != 'video_asset': if xml.tag != 'video_asset':
raise ValCannotCreateError('Invalid XML') raise ValCannotCreateError('Invalid XML')
# if edx_video_id does not exist then create video transcripts only # TODO this will be moved as a part of EDUCATOR-2173
if not edx_video_id: if not edx_video_id:
return create_transcript_objects(xml) return
# If video with edx_video_id already exists, associate it with the given course_id. # If video with edx_video_id already exists, associate it with the given course_id.
try: try:
...@@ -885,9 +873,6 @@ def import_from_xml(xml, edx_video_id, course_id=None): ...@@ -885,9 +873,6 @@ def import_from_xml(xml, edx_video_id, course_id=None):
if image_file_name: if image_file_name:
VideoImage.create_or_update(course_video, image_file_name) VideoImage.create_or_update(course_video, image_file_name)
# import transcripts
create_transcript_objects(xml)
return return
except ValidationError as err: except ValidationError as err:
logger.exception(err.message) logger.exception(err.message)
...@@ -934,7 +919,7 @@ def create_transcript_objects(xml): ...@@ -934,7 +919,7 @@ def create_transcript_objects(xml):
""" """
for transcript in xml.findall('.//transcripts/transcript'): for transcript in xml.findall('.//transcripts/transcript'):
try: try:
VideoTranscript.create_or_update( create_or_update_video_transcript(
transcript.attrib['video_id'], transcript.attrib['video_id'],
transcript.attrib['language_code'], transcript.attrib['language_code'],
metadata=dict( metadata=dict(
......
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('edxval', '0009_auto_20171127_0406'),
]
operations = [
migrations.AlterUniqueTogether(
name='videotranscript',
unique_together=set([('language_code',)]),
),
migrations.RemoveField(
model_name='videotranscript',
name='video_id',
),
migrations.AddField(
model_name='videotranscript',
name='video',
field=models.ForeignKey(related_name='video_transcripts', to='edxval.Video', null=True),
),
migrations.AlterUniqueTogether(
name='videotranscript',
unique_together=set([('video', 'language_code')]),
),
]
...@@ -404,7 +404,7 @@ class VideoTranscript(TimeStampedModel): ...@@ -404,7 +404,7 @@ class VideoTranscript(TimeStampedModel):
""" """
Transcript for a video Transcript for a video
""" """
video_id = models.CharField(max_length=255, help_text='It can be an edx_video_id or an external video id') video = models.ForeignKey(Video, related_name='video_transcripts', null=True)
transcript = CustomizableFileField() transcript = CustomizableFileField()
language_code = models.CharField(max_length=50, db_index=True) language_code = models.CharField(max_length=50, db_index=True)
provider = models.CharField( provider = models.CharField(
...@@ -415,27 +415,19 @@ class VideoTranscript(TimeStampedModel): ...@@ -415,27 +415,19 @@ class VideoTranscript(TimeStampedModel):
file_format = models.CharField(max_length=20, db_index=True, choices=TranscriptFormat.CHOICES) file_format = models.CharField(max_length=20, db_index=True, choices=TranscriptFormat.CHOICES)
class Meta: class Meta:
unique_together = ('video_id', 'language_code') unique_together = ('video', 'language_code')
@property @property
def filename(self): def filename(self):
""" """
Returns readable filename for a transcript Returns readable filename for a transcript
""" """
try: client_id, __ = os.path.splitext(self.video.client_video_id)
video = Video.objects.get(edx_video_id=self.video_id)
client_id, __ = os.path.splitext(video.client_video_id)
file_name = u'{name}-{language}.{format}'.format( file_name = u'{name}-{language}.{format}'.format(
name=client_id, name=client_id,
language=self.language_code, language=self.language_code,
format=self.file_format format=self.file_format
) )
except Video.DoesNotExist:
file_name = u'{name}-{language}.{format}'.format(
name=self.video_id,
language=self.language_code,
format=self.file_format
)
return file_name return file_name
...@@ -449,19 +441,19 @@ class VideoTranscript(TimeStampedModel): ...@@ -449,19 +441,19 @@ class VideoTranscript(TimeStampedModel):
language_code(unicode): language of the requested transcript language_code(unicode): language of the requested transcript
""" """
try: try:
transcript = cls.objects.get(video_id=video_id, language_code=language_code) transcript = cls.objects.get(video__edx_video_id=video_id, language_code=language_code)
except cls.DoesNotExist: except cls.DoesNotExist:
transcript = None transcript = None
return transcript return transcript
@classmethod @classmethod
def create_or_update(cls, video_id, language_code, metadata, file_data=None): def create_or_update(cls, video, language_code, metadata, file_data=None):
""" """
Create or update Transcript object. Create or update Transcript object.
Arguments: Arguments:
video_id (str): unique id for a video video (Video): Video for which transcript is going to be saved.
language_code (str): language code for (to be created/updated) transcript language_code (str): language code for (to be created/updated) transcript
metadata (dict): A dict containing (to be overwritten) properties metadata (dict): A dict containing (to be overwritten) properties
file_data (InMemoryUploadedFile): File data to be saved file_data (InMemoryUploadedFile): File data to be saved
...@@ -469,7 +461,7 @@ class VideoTranscript(TimeStampedModel): ...@@ -469,7 +461,7 @@ class VideoTranscript(TimeStampedModel):
Returns: Returns:
Returns a tuple of (video_transcript, created). Returns a tuple of (video_transcript, created).
""" """
video_transcript, created = cls.objects.get_or_create(video_id=video_id, language_code=language_code) video_transcript, created = cls.objects.get_or_create(video=video, language_code=language_code)
for prop, value in metadata.iteritems(): for prop, value in metadata.iteritems():
if prop in ['language_code', 'file_format', 'provider']: if prop in ['language_code', 'file_format', 'provider']:
...@@ -489,7 +481,7 @@ class VideoTranscript(TimeStampedModel): ...@@ -489,7 +481,7 @@ class VideoTranscript(TimeStampedModel):
try: try:
video_transcript.transcript.save(file_name, transcript_file_data) video_transcript.transcript.save(file_name, transcript_file_data)
except Exception: except Exception:
logger.exception('VAL: Transcript save failed to storage for video_id [%s]', video_id) logger.exception('VAL: Transcript save failed to storage for video_id [%s]', video.edx_video_id)
raise raise
video_transcript.save() video_transcript.save()
...@@ -503,7 +495,7 @@ class VideoTranscript(TimeStampedModel): ...@@ -503,7 +495,7 @@ class VideoTranscript(TimeStampedModel):
return storage.url(self.transcript.name) return storage.url(self.transcript.name)
def __unicode__(self): def __unicode__(self):
return u'{lang} Transcript for {video}'.format(lang=self.language_code, video=self.video_id) return u'{lang} Transcript for {video}'.format(lang=self.language_code, video=self.video.edx_video_id)
class Cielo24Turnaround(object): class Cielo24Turnaround(object):
......
...@@ -57,11 +57,17 @@ class TranscriptSerializer(serializers.ModelSerializer): ...@@ -57,11 +57,17 @@ class TranscriptSerializer(serializers.ModelSerializer):
""" """
class Meta: # pylint: disable=C1001, C0111 class Meta: # pylint: disable=C1001, C0111
model = VideoTranscript model = VideoTranscript
lookup_field = 'video_id'
fields = ('video_id', 'url', 'language_code', 'provider', 'file_format') fields = ('video_id', 'url', 'language_code', 'provider', 'file_format')
video_id = serializers.SerializerMethodField()
url = serializers.SerializerMethodField() url = serializers.SerializerMethodField()
def get_video_id(self, transcript):
"""
Returns an edx video ID for the related video.
"""
return transcript.video.edx_video_id
def get_url(self, transcript): def get_url(self, transcript):
""" """
Retrieves the transcript url. Retrieves the transcript url.
......
...@@ -378,14 +378,6 @@ VIDEO_TRANSCRIPT_3PLAY = dict( ...@@ -378,14 +378,6 @@ VIDEO_TRANSCRIPT_3PLAY = dict(
file_format=TranscriptFormat.SJSON, file_format=TranscriptFormat.SJSON,
) )
VIDEO_TRANSCRIPT_CUSTOM = dict(
video_id='external_video_id',
language_code='de',
transcript='wow.srt',
provider=TranscriptProviderType.CUSTOM,
file_format=TranscriptFormat.SRT,
)
TRANSCRIPT_PREFERENCES_CIELO24 = dict( TRANSCRIPT_PREFERENCES_CIELO24 = dict(
course_id='edX/DemoX/Demo_Course', course_id='edX/DemoX/Demo_Course',
provider=TranscriptProviderType.CIELO24, provider=TranscriptProviderType.CIELO24,
......
...@@ -4,7 +4,6 @@ Tests for the API for Video Abstraction Layer ...@@ -4,7 +4,6 @@ Tests for the API for Video Abstraction Layer
""" """
import json import json
import os import os
import tempfile
import mock import mock
from ddt import data, ddt, unpack from ddt import data, ddt, unpack
...@@ -908,16 +907,13 @@ class ExportTest(TestCase): ...@@ -908,16 +907,13 @@ class ExportTest(TestCase):
**constants.ENCODED_VIDEO_DICT_HLS **constants.ENCODED_VIDEO_DICT_HLS
) )
# create external video transcripts
VideoTranscript.objects.create(**constants.VIDEO_TRANSCRIPT_CUSTOM)
video_transcript = dict(constants.VIDEO_TRANSCRIPT_CUSTOM, language_code=u'ar')
VideoTranscript.objects.create(**video_transcript)
video_transcript = dict(constants.VIDEO_TRANSCRIPT_CUSTOM, video_id=u'external_video_id2', language_code=u'fr')
VideoTranscript.objects.create(**video_transcript)
# create internal video transcripts # create internal video transcripts
VideoTranscript.objects.create(**constants.VIDEO_TRANSCRIPT_CIELO24) transcript_data = dict(constants.VIDEO_TRANSCRIPT_CIELO24, video=video)
VideoTranscript.objects.create(**constants.VIDEO_TRANSCRIPT_3PLAY) transcript_data.pop('video_id')
VideoTranscript.objects.create(**transcript_data)
transcript_data = dict(constants.VIDEO_TRANSCRIPT_3PLAY, video=video)
transcript_data.pop('video_id')
VideoTranscript.objects.create(**transcript_data)
def assert_xml_equal(self, left, right): def assert_xml_equal(self, left, right):
""" """
...@@ -949,8 +945,8 @@ class ExportTest(TestCase): ...@@ -949,8 +945,8 @@ class ExportTest(TestCase):
) )
@data( @data(
{'course_id': None, 'image':''}, {'course_id': None, 'image': ''},
{'course_id': 'test-course', 'image':'image.jpg'}, {'course_id': 'test-course', 'image': 'image.jpg'},
) )
@unpack @unpack
def test_basic(self, course_id, image): def test_basic(self, course_id, image):
...@@ -960,8 +956,8 @@ class ExportTest(TestCase): ...@@ -960,8 +956,8 @@ class ExportTest(TestCase):
<encoded_video url="http://www.meowmagic.com" file_size="33" bitrate="44" profile="desktop"/> <encoded_video url="http://www.meowmagic.com" file_size="33" bitrate="44" profile="desktop"/>
<encoded_video url="https://www.tmnt.com/tmnt101.m3u8" file_size="100" bitrate="0" profile="hls"/> <encoded_video url="https://www.tmnt.com/tmnt101.m3u8" file_size="100" bitrate="0" profile="hls"/>
<transcripts> <transcripts>
<transcript file_format="sjson" file_name="edxval/tests/data/wow.sjson" language_code="de" provider="3PlayMedia" video_id="super-soaker"/> <transcript file_format="sjson" file_name="edxval/tests/data/wow.sjson" language_code="de" provider="3PlayMedia" video_id="{video_id}"/>
<transcript file_format="srt" file_name="wow.srt" language_code="en" provider="Cielo24" video_id="super-soaker" /> <transcript file_format="srt" file_name="wow.srt" language_code="en" provider="Cielo24" video_id="{video_id}" />
</transcripts> </transcripts>
</video_asset> </video_asset>
""".format(image=image, video_id=constants.VIDEO_DICT_FISH['edx_video_id'])) """.format(image=image, video_id=constants.VIDEO_DICT_FISH['edx_video_id']))
...@@ -975,26 +971,6 @@ class ExportTest(TestCase): ...@@ -975,26 +971,6 @@ class ExportTest(TestCase):
with self.assertRaises(ValVideoNotFoundError): with self.assertRaises(ValVideoNotFoundError):
api.export_to_xml(["unknown_video"]) api.export_to_xml(["unknown_video"])
def test_external_video_transcript(self):
"""
Verify that transcript export for multiple external videos is working as expected.
"""
video_ids = ['missing', 'external_video_id', 'missing2', 'external_video_id2']
expected = self.parse_xml("""
<video_asset>
<transcripts>
<transcript file_format="srt" file_name="wow.srt" language_code="ar" provider="Custom" video_id="external_video_id"/>
<transcript file_format="srt" file_name="wow.srt" language_code="de" provider="Custom" video_id="external_video_id"/>
<transcript file_format="srt" file_name="wow.srt" language_code="fr" provider="Custom" video_id="external_video_id2"/>
</transcripts>
</video_asset>
""".format(video_id=''))
self.assert_xml_equal(
api.export_to_xml(video_ids, external=True),
expected
)
def test_with_multiple_video_ids(self): def test_with_multiple_video_ids(self):
""" """
Verify that transcript export with multiple video ids is working as expected. Verify that transcript export with multiple video ids is working as expected.
...@@ -1006,8 +982,7 @@ class ExportTest(TestCase): ...@@ -1006,8 +982,7 @@ class ExportTest(TestCase):
<encoded_video bitrate="44" file_size="33" profile="desktop" url="http://www.meowmagic.com" /> <encoded_video bitrate="44" file_size="33" profile="desktop" url="http://www.meowmagic.com" />
<encoded_video bitrate="0" file_size="100" profile="hls" url="https://www.tmnt.com/tmnt101.m3u8" /> <encoded_video bitrate="0" file_size="100" profile="hls" url="https://www.tmnt.com/tmnt101.m3u8" />
<transcripts> <transcripts>
<transcript file_format="srt" file_name="wow.srt" language_code="ar" provider="Custom" video_id="external_video_id" /> <transcript file_format="sjson" file_name="edxval/tests/data/wow.sjson" language_code="de" provider="3PlayMedia" video_id="super-soaker"/>
<transcript file_format="srt" file_name="wow.srt" language_code="de" provider="Custom" video_id="external_video_id"/>
<transcript file_format="srt" file_name="wow.srt" language_code="en" provider="Cielo24" video_id="super-soaker" /> <transcript file_format="srt" file_name="wow.srt" language_code="en" provider="Cielo24" video_id="super-soaker" />
</transcripts> </transcripts>
</video_asset> </video_asset>
...@@ -1115,7 +1090,7 @@ class ImportTest(TestCase): ...@@ -1115,7 +1090,7 @@ class ImportTest(TestCase):
Compare `received` with `expected` and assert if not equal Compare `received` with `expected` and assert if not equal
""" """
# Verify total number of expected transcripts for a video # Verify total number of expected transcripts for a video
video_transcripts = VideoTranscript.objects.filter(video_id=video_id) video_transcripts = VideoTranscript.objects.filter(video__edx_video_id=video_id)
self.assertEqual(video_transcripts.count(), len(expected_transcripts)) self.assertEqual(video_transcripts.count(), len(expected_transcripts))
# Verify data for each transcript # Verify data for each transcript
...@@ -1125,7 +1100,7 @@ class ImportTest(TestCase): ...@@ -1125,7 +1100,7 @@ class ImportTest(TestCase):
# get the imported transcript and rename `url` key # get the imported transcript and rename `url` key
received = api.TranscriptSerializer( received = api.TranscriptSerializer(
VideoTranscript.objects.get(video_id=video_id, language_code=language_code) VideoTranscript.objects.get(video__edx_video_id=video_id, language_code=language_code)
).data ).data
received['name'] = received.pop('url') received['name'] = received.pop('url')
...@@ -1143,7 +1118,7 @@ class ImportTest(TestCase): ...@@ -1143,7 +1118,7 @@ class ImportTest(TestCase):
# there must not be any transcript before import # there must not be any transcript before import
with self.assertRaises(VideoTranscript.DoesNotExist): with self.assertRaises(VideoTranscript.DoesNotExist):
VideoTranscript.objects.get(video_id=constants.VIDEO_DICT_STAR['edx_video_id']) VideoTranscript.objects.get(video__edx_video_id=constants.VIDEO_DICT_STAR['edx_video_id'])
api.import_from_xml(xml, constants.VIDEO_DICT_STAR['edx_video_id'], new_course_id) api.import_from_xml(xml, constants.VIDEO_DICT_STAR['edx_video_id'], new_course_id)
...@@ -1209,7 +1184,7 @@ class ImportTest(TestCase): ...@@ -1209,7 +1184,7 @@ class ImportTest(TestCase):
# there must not be any transcript before import # there must not be any transcript before import
with self.assertRaises(VideoTranscript.DoesNotExist): with self.assertRaises(VideoTranscript.DoesNotExist):
VideoTranscript.objects.get(video_id=constants.VIDEO_DICT_FISH["edx_video_id"]) VideoTranscript.objects.get(video__edx_video_id=constants.VIDEO_DICT_FISH["edx_video_id"])
api.import_from_xml(xml, constants.VIDEO_DICT_FISH["edx_video_id"], course_id) api.import_from_xml(xml, constants.VIDEO_DICT_FISH["edx_video_id"], course_id)
...@@ -1228,7 +1203,7 @@ class ImportTest(TestCase): ...@@ -1228,7 +1203,7 @@ class ImportTest(TestCase):
self.assert_transcripts( self.assert_transcripts(
constants.VIDEO_DICT_FISH["edx_video_id"], constants.VIDEO_DICT_FISH["edx_video_id"],
[transcript_data] []
) )
def test_existing_video_with_invalid_course_id(self): def test_existing_video_with_invalid_course_id(self):
...@@ -1290,26 +1265,6 @@ class ImportTest(TestCase): ...@@ -1290,26 +1265,6 @@ class ImportTest(TestCase):
xml = self.make_import_xml(video_dict=constants.VIDEO_DICT_FISH) xml = self.make_import_xml(video_dict=constants.VIDEO_DICT_FISH)
self.assert_invalid_import(xml, "x" * 300) self.assert_invalid_import(xml, "x" * 300)
def test_external_video_transcript(self):
"""
Verify that transcript import for external video working as expected.
"""
external_video_id = 'little-star'
xml = etree.fromstring("""
<video_asset>
<transcripts>
<transcript file_name="wow.srt" language_code="en" file_format="srt" provider='Cielo24' video_id="{video_id}"/>
<transcript file_name="edxval/tests/data/wow.sjson" language_code="de" file_format="sjson" provider='3PlayMedia' video_id="{video_id}"/>
</transcripts>
</video_asset>
""".format(video_id=external_video_id))
with self.assertRaises(VideoTranscript.DoesNotExist):
VideoTranscript.objects.get(video_id=external_video_id)
api.import_from_xml(xml, '')
self.assert_transcripts(external_video_id, [self.transcript_data1, self.transcript_data2])
def test_external_no_video_transcript(self): def test_external_no_video_transcript(self):
""" """
Verify that transcript import for external video working as expected when there is no transcript. Verify that transcript import for external video working as expected when there is no transcript.
...@@ -1325,7 +1280,7 @@ class ImportTest(TestCase): ...@@ -1325,7 +1280,7 @@ class ImportTest(TestCase):
""" """
Verify that video transcript import working as expected if transcript xml data is missing. Verify that video transcript import working as expected if transcript xml data is missing.
""" """
video_id = 'little-star' video_id = 'super-soaker'
transcript_xml = '<transcript file_name="wow.srt" language_code="en" file_format="srt" provider="Cielo24"/>' transcript_xml = '<transcript file_name="wow.srt" language_code="en" file_format="srt" provider="Cielo24"/>'
xml = etree.fromstring(""" xml = etree.fromstring("""
<video_asset> <video_asset>
...@@ -1338,7 +1293,7 @@ class ImportTest(TestCase): ...@@ -1338,7 +1293,7 @@ class ImportTest(TestCase):
# there should be no video transcript before import # there should be no video transcript before import
with self.assertRaises(VideoTranscript.DoesNotExist): with self.assertRaises(VideoTranscript.DoesNotExist):
VideoTranscript.objects.get(video_id=video_id) VideoTranscript.objects.get(video__edx_video_id=video_id)
api.create_transcript_objects(xml) api.create_transcript_objects(xml)
...@@ -1347,7 +1302,7 @@ class ImportTest(TestCase): ...@@ -1347,7 +1302,7 @@ class ImportTest(TestCase):
transcript_xml transcript_xml
) )
self.assert_transcripts(video_id, [self.transcript_data2]) self.assert_transcripts(video_id, [self.transcript_data3])
class GetCourseVideoRemoveTest(TestCase): class GetCourseVideoRemoveTest(TestCase):
...@@ -1676,60 +1631,94 @@ class TranscriptTest(TestCase): ...@@ -1676,60 +1631,94 @@ class TranscriptTest(TestCase):
""" """
Creates video and video transcript objects. Creates video and video transcript objects.
""" """
self.video1 = Video.objects.create(**constants.VIDEO_DICT_FISH)
self.edx_video_id1 = self.video1.edx_video_id
self.video2 = Video.objects.create(**constants.VIDEO_DICT_DIFFERENT_ID_FISH)
self.edx_video_id2 = self.video2.edx_video_id
self.transcript_data1 = dict(constants.VIDEO_TRANSCRIPT_CIELO24)
self.transcript_data1['name'] = self.transcript_data1.pop('transcript')
self.transcript_data2 = dict(constants.VIDEO_TRANSCRIPT_3PLAY)
self.transcript_data2['name'] = self.transcript_data2.pop('transcript')
self.transcript1 = VideoTranscript.objects.create(**constants.VIDEO_TRANSCRIPT_CIELO24)
self.transcript2 = VideoTranscript.objects.create(**constants.VIDEO_TRANSCRIPT_3PLAY)
self.video_id = '0987654321'
self.arrow_transcript_path = 'edxval/tests/data/The_Arrow.srt'
self.flash_transcript_path = 'edxval/tests/data/The_Flash.srt' self.flash_transcript_path = 'edxval/tests/data/The_Flash.srt'
self.transcript_url = api.create_or_update_video_transcript( self.arrow_transcript_path = 'edxval/tests/data/The_Arrow.srt'
self.video_id,
'ur', # Set up a video (video_id -> super-soaker) with 'en' and 'fr' transcripts.
metadata={'file_format': TranscriptFormat.SRT}, video_and_transcripts = self.setup_video_with_transcripts(
file_data=File(open(self.arrow_transcript_path)) video_data=constants.VIDEO_DICT_FISH,
transcripts_data=[
{
'language_code': 'en',
'provider': TranscriptProviderType.THREE_PLAY_MEDIA,
'file_name': None,
'file_format': TranscriptFormat.SRT,
'file_data': File(open(self.flash_transcript_path))
},
{
'language_code': 'fr',
'provider': TranscriptProviderType.CIELO24,
'file_name': None,
'file_format': TranscriptFormat.SRT,
'file_data': ContentFile(FILE_DATA)
}
]
) )
self.video1 = video_and_transcripts['video']
self.v1_transcript1 = video_and_transcripts['transcripts']['en']
self.v1_transcript2 = video_and_transcripts['transcripts']['fr']
# create a temporary transcript file # Set up another video (video_id -> medium-soaker) with 'de' and 'zh' transcripts.
_, self.transcript_file = tempfile.mkstemp( video_and_transcripts = self.setup_video_with_transcripts(
suffix='.srt', video_data=constants.VIDEO_DICT_DIFFERENT_ID_FISH,
dir='edxval/tests/data/' transcripts_data=[
{
'language_code': 'de',
'provider': TranscriptProviderType.CUSTOM,
'file_name': None,
'file_format': TranscriptFormat.SRT,
'file_data': File(open(self.arrow_transcript_path))
},
{
'language_code': 'zh',
'provider': TranscriptProviderType.CUSTOM,
'file_name': 'non/existent/transcript/path',
'file_format': TranscriptFormat.SRT,
'file_data': None
}
]
) )
with open(self.transcript_file, 'w') as outfile: self.video2 = video_and_transcripts['video']
outfile.write(FILE_DATA) self.v2_transcript1 = video_and_transcripts['transcripts']['de']
self.v2_transcript2 = video_and_transcripts['transcripts']['zh']
self.transcript3 = VideoTranscript.objects.create( def setup_video_with_transcripts(self, video_data, transcripts_data):
video_id='super-soaker', """
language_code='fr', Setup a video with transcripts and returns them
transcript=self.transcript_file, """
provider=TranscriptProviderType.THREE_PLAY_MEDIA, result = dict(video=None, transcripts={})
file_format=TranscriptFormat.SRT, result['video'] = Video.objects.create(**video_data)
for transcript_data in transcripts_data:
language_code = transcript_data['language_code']
transcript, __ = VideoTranscript.create_or_update(
video=result['video'],
language_code=language_code,
metadata={
'file_name': transcript_data['file_name'],
'file_format': transcript_data['file_format'],
'provider': transcript_data['provider']
},
file_data=transcript_data['file_data']
) )
result['transcripts'][language_code] = transcript
return result
def tearDown(self): def tearDown(self):
""" """
Reverse the setup Reverse the setup
""" """
# Remove the temporary transcript file # Remove the transcript files
if os.path.exists(self.transcript_file): self.v1_transcript1.transcript.delete()
os.remove(self.transcript_file) self.v1_transcript2.transcript.delete()
self.v2_transcript1.transcript.delete()
@data( @data(
{'video_id': 'super-soaker', 'language_code': 'en', 'expected_availability': True}, {'video_id': 'super-soaker', 'language_code': 'en', 'expected_availability': True},
{'video_id': 'super-soaker', 'language_code': None, 'expected_availability': True}, {'video_id': 'super-soaker', 'language_code': None, 'expected_availability': True},
{'video_id': 'super123', 'language_code': 'en', 'expected_availability': False}, {'video_id': 'super123', 'language_code': 'en', 'expected_availability': False},
{'video_id': 'super-soaker', 'language_code': 'ro', 'expected_availability': False}, {'video_id': 'super-soaker', 'language_code': 'ro', 'expected_availability': False},
{'video_id': 'medium-soaker', 'language_code': 'de', 'expected_availability': True},
) )
@unpack @unpack
def test_is_transcript_available(self, video_id, language_code, expected_availability): def test_is_transcript_available(self, video_id, language_code, expected_availability):
...@@ -1754,13 +1743,13 @@ class TranscriptTest(TestCase): ...@@ -1754,13 +1743,13 @@ class TranscriptTest(TestCase):
""" """
Verify that `get_video_transcript` works as expected if transcript is found. Verify that `get_video_transcript` works as expected if transcript is found.
""" """
transcript = api.get_video_transcript(u'0987654321', u'ur') transcript = api.get_video_transcript(u'super-soaker', u'fr')
expectation = { expectation = {
'video_id': u'0987654321', 'video_id': u'super-soaker',
'url': self.transcript_url, 'url': self.v1_transcript2.url(),
'file_format': TranscriptFormat.SRT, 'file_format': TranscriptFormat.SRT,
'provider': TranscriptProviderType.CUSTOM, 'provider': TranscriptProviderType.CIELO24,
'language_code': u'ur' 'language_code': u'fr'
} }
self.assertDictEqual(transcript, expectation) self.assertDictEqual(transcript, expectation)
...@@ -1770,17 +1759,17 @@ class TranscriptTest(TestCase): ...@@ -1770,17 +1759,17 @@ class TranscriptTest(TestCase):
Verify that `get_video_transcript_data` logs and raises an exception. Verify that `get_video_transcript_data` logs and raises an exception.
""" """
with self.assertRaises(IOError): with self.assertRaises(IOError):
api.get_video_transcript_data(video_ids=['super-soaker'], language_code=u'en') api.get_video_transcript_data(video_ids=['medium-soaker'], language_code=u'zh')
mock_logger.exception.assert_called_with( mock_logger.exception.assert_called_with(
'[edx-val] Error while retrieving transcript for video=%s -- language_code=%s', '[edx-val] Error while retrieving transcript for video=%s -- language_code=%s',
'super-soaker', 'medium-soaker',
'en', 'zh',
) )
@data( @data(
{'video_ids': ['non-existant-video', 'another-non-existant-id'], 'language_code': 'en', 'result': None}, {'video_ids': ['non-existant-video', 'another-non-existant-id'], 'language_code': 'en', 'result': None},
{'video_ids': ['non-existant-video', '0987654321'], 'language_code': 'en', 'result': None}, {'video_ids': ['non-existant-video', 'super-soaker'], 'language_code': 'zh', 'result': None},
) )
@unpack @unpack
def test_get_video_transcript_data_not_found(self, video_ids, language_code, result): def test_get_video_transcript_data_not_found(self, video_ids, language_code, result):
...@@ -1791,11 +1780,11 @@ class TranscriptTest(TestCase): ...@@ -1791,11 +1780,11 @@ class TranscriptTest(TestCase):
self.assertEqual(transcript, result) self.assertEqual(transcript, result)
@data( @data(
('de', 'Shallow Swordfish-de.sjson', 'edxval/tests/data/wow.sjson'), ('super-soaker', 'en', 'Shallow Swordfish-en.srt', 'edxval/tests/data/The_Flash.srt'),
('ur', '0987654321-ur.srt', 'edxval/tests/data/The_Arrow.srt') ('medium-soaker', 'de', 'Shallow Swordfish-de.srt', 'edxval/tests/data/The_Arrow.srt')
) )
@unpack @unpack
def test_get_video_transcript_data(self, language_code, expected_file_name, expected_transcript_path): def test_get_video_transcript_data(self, video_id, language_code, expected_file_name, expected_transcript_path):
""" """
Verify that `get_video_transcript_data` api function works as expected. Verify that `get_video_transcript_data` api function works as expected.
""" """
...@@ -1804,75 +1793,17 @@ class TranscriptTest(TestCase): ...@@ -1804,75 +1793,17 @@ class TranscriptTest(TestCase):
'content': File(open(expected_transcript_path)).read() 'content': File(open(expected_transcript_path)).read()
} }
transcript = api.get_video_transcript_data( transcript = api.get_video_transcript_data(
video_ids=['super-soaker', '0987654321'], video_ids=[video_id, '0987654321'],
language_code=language_code language_code=language_code
) )
self.assertDictEqual(transcript, expected_transcript) self.assertDictEqual(transcript, expected_transcript)
@data( def test_get_video_transcript_url(self):
{'video_id': 'super-soaker', 'result': True},
{'video_id': 'super-soaker1', 'result': False},
)
@unpack
def test_get_video_transcripts(self, video_id, result):
"""
Verify that `get_video_transcripts` api function works as expected.
"""
transcripts = api.get_video_transcripts(video_id)
if result:
self.assertEqual(len(transcripts), 3)
for transcript, transcript_data in zip(transcripts, [self.transcript_data2, self.transcript_data1]):
transcript_data['url'] = transcript_data.pop('name')
self.assertEqual(transcript, transcript_data)
else:
self.assertEqual(transcripts, [])
def test_create_video_transcript(self):
"""
Verify that `create_or_update_video_transcript` api function creates transcript if there is no already.
"""
transcript_data = dict(self.transcript_data1)
transcript_data['language_code'] = 'ur'
with self.assertRaises(VideoTranscript.DoesNotExist):
VideoTranscript.objects.get(
video_id=transcript_data['video_id'],
language_code=transcript_data['language_code']
)
transcript_url = api.create_or_update_video_transcript(
video_id=transcript_data['video_id'],
language_code=transcript_data['language_code'],
metadata=dict(
file_name=transcript_data['name'],
file_format=transcript_data['file_format'],
provider=transcript_data['provider']
)
)
self.assertEqual(transcript_url, transcript_data['name'])
expected_transcript = api.get_video_transcript(
video_id=transcript_data['video_id'],
language_code=transcript_data['language_code']
)
transcript_data['url'] = transcript_data.pop('name')
self.assertEqual(transcript_data, expected_transcript)
@data(
{'language_code': 'ur', 'has_url': True},
{'language_code': 'xyz', 'has_url': False},
)
@unpack
def test_get_video_transcript_url(self, language_code, has_url):
""" """
Verify that `get_video_transcript_url` api function works as expected. Verify that `get_video_transcript_url` api function works as expected.
""" """
transcript_url = api.get_video_transcript_url(self.video_id, language_code) transcript_url = api.get_video_transcript_url('super-soaker', 'fr')
if has_url: self.assertEqual(transcript_url, self.v1_transcript2.url())
self.assertEqual(self.transcript_url, transcript_url)
else:
self.assertIsNone(transcript_url)
@data( @data(
{ {
...@@ -1895,12 +1826,13 @@ class TranscriptTest(TestCase): ...@@ -1895,12 +1826,13 @@ class TranscriptTest(TestCase):
""" """
Verify that `create_or_update_video_transcript` api function updates existing transcript as expected. Verify that `create_or_update_video_transcript` api function updates existing transcript as expected.
""" """
video_transcript = VideoTranscript.objects.get(video_id=self.video_id, language_code='ur') edx_video_id = 'super-soaker'
video_transcript = VideoTranscript.objects.get(video__edx_video_id=edx_video_id, language_code='en')
self.assertIsNotNone(video_transcript) self.assertIsNotNone(video_transcript)
transcript_url = api.create_or_update_video_transcript( transcript_url = api.create_or_update_video_transcript(
video_id=self.video_id, video_id=edx_video_id,
language_code='ur', language_code='en',
metadata=dict( metadata=dict(
provider=provider, provider=provider,
language_code=language_code, language_code=language_code,
...@@ -1912,10 +1844,10 @@ class TranscriptTest(TestCase): ...@@ -1912,10 +1844,10 @@ class TranscriptTest(TestCase):
# Now, Querying Video Transcript with previous/old language code leads to DoesNotExist # Now, Querying Video Transcript with previous/old language code leads to DoesNotExist
with self.assertRaises(VideoTranscript.DoesNotExist): with self.assertRaises(VideoTranscript.DoesNotExist):
VideoTranscript.objects.get(video_id=self.video_id, language_code='ur') VideoTranscript.objects.get(video__edx_video_id=edx_video_id, language_code='en')
# Assert the updates to the transcript object # Assert the updates to the transcript object
video_transcript = VideoTranscript.objects.get(video_id=self.video_id, language_code=language_code) video_transcript = VideoTranscript.objects.get(video__edx_video_id=edx_video_id, language_code=language_code)
self.assertEqual(transcript_url, video_transcript.url()) self.assertEqual(transcript_url, video_transcript.url())
self.assertEqual(video_transcript.file_format, file_format) self.assertEqual(video_transcript.file_format, file_format)
self.assertEqual(video_transcript.provider, provider) self.assertEqual(video_transcript.provider, provider)
...@@ -1931,12 +1863,14 @@ class TranscriptTest(TestCase): ...@@ -1931,12 +1863,14 @@ class TranscriptTest(TestCase):
@data( @data(
{ {
'video_id': 'super-soaker',
'file_format': '123', 'file_format': '123',
'provider': TranscriptProviderType.CIELO24, 'provider': TranscriptProviderType.CIELO24,
'exception': InvalidTranscriptFormat, 'exception': InvalidTranscriptFormat,
'exception_message': '123 transcript format is not supported', 'exception_message': '123 transcript format is not supported',
}, },
{ {
'video_id': 'medium-soaker',
'file_format': TranscriptFormat.SRT, 'file_format': TranscriptFormat.SRT,
'provider': 123, 'provider': 123,
'exception': InvalidTranscriptProvider, 'exception': InvalidTranscriptProvider,
...@@ -1944,12 +1878,12 @@ class TranscriptTest(TestCase): ...@@ -1944,12 +1878,12 @@ class TranscriptTest(TestCase):
}, },
) )
@unpack @unpack
def test_create_or_update_video_exceptions(self, file_format, provider, exception, exception_message): def test_create_or_update_video_exceptions(self, video_id, file_format, provider, exception, exception_message):
""" """
Verify that `create_or_update_video_transcript` api function raise exceptions on invalid values. Verify that `create_or_update_video_transcript` api function raise exceptions on invalid values.
""" """
with self.assertRaises(exception) as transcript_exception: with self.assertRaises(exception) as transcript_exception:
api.create_or_update_video_transcript(self.video_id, 'ur', metadata={ api.create_or_update_video_transcript(video_id, 'ur', metadata={
'provider': provider, 'provider': provider,
'file_format': file_format 'file_format': file_format
}) })
...@@ -1960,21 +1894,22 @@ class TranscriptTest(TestCase): ...@@ -1960,21 +1894,22 @@ class TranscriptTest(TestCase):
""" """
Test video transcript deletion works as expected. Test video transcript deletion works as expected.
""" """
edx_video_id = 'super-soaker'
# get an existing video transcript # get an existing video transcript
video_transcript = VideoTranscript.objects.get(video_id=self.video_id, language_code='ur') video_transcript = VideoTranscript.objects.get(video__edx_video_id=edx_video_id, language_code='en')
existing_transcript_url = video_transcript.transcript.name existing_transcript_url = video_transcript.url()
# This will replace the transcript for an existing video and delete the existing transcript # This will replace the transcript for an existing video and delete the existing transcript
new_transcript_url = api.create_or_update_video_transcript( new_transcript_url = api.create_or_update_video_transcript(
video_id=self.video_id, video_id=edx_video_id,
language_code='ur', language_code='en',
metadata=dict(provider=TranscriptProviderType.CIELO24), metadata=dict(provider=TranscriptProviderType.CIELO24),
file_data=ContentFile(FILE_DATA) file_data=ContentFile(FILE_DATA)
) )
# Verify that new transcript is set to video # Verify that new transcript is set to video
video_transcript = VideoTranscript.objects.get(video_id=self.video_id, language_code='ur') video_transcript = VideoTranscript.objects.get(video__edx_video_id=edx_video_id, language_code='en')
self.assertEqual(video_transcript.transcript.name, new_transcript_url) self.assertEqual(video_transcript.url(), new_transcript_url)
# verify that new data is written correctly # verify that new data is written correctly
with open(video_transcript.transcript.name) as saved_transcript: with open(video_transcript.transcript.name) as saved_transcript:
...@@ -1990,28 +1925,30 @@ class TranscriptTest(TestCase): ...@@ -1990,28 +1925,30 @@ class TranscriptTest(TestCase):
""" """
Verify that `get_available_transcript_languages` works as expected. Verify that `get_available_transcript_languages` works as expected.
""" """
dupe_lang_video_id = 'duplicate_lang_video' # `super-soaker` has got 'en' and 'fr' transcripts
VideoTranscript.objects.create(**dict(constants.VIDEO_TRANSCRIPT_CIELO24, video_id=dupe_lang_video_id))
# `super-soaker` has got 'en' and 'de' transcripts
# `self.video_id` has got 'ur' transcript
# `duplicate_lang_video` has got 'en' transcript
# `non_existent_video_id` that does not have transcript # `non_existent_video_id` that does not have transcript
video_ids = ['super-soaker', self.video_id, dupe_lang_video_id, 'non_existent_video_id'] video_ids = ['super-soaker', 'non_existent_video_id']
transcript_languages = api.get_available_transcript_languages(video_ids=video_ids) transcript_languages = api.get_available_transcript_languages(video_ids=video_ids)
self.assertItemsEqual(transcript_languages, ['de', 'en', 'ur', 'fr']) self.assertItemsEqual(transcript_languages, ['en', 'fr'])
def test_delete_video_transcript(self): def test_delete_video_transcript(self):
""" """
Verify that `delete_video_transcript` works as expected. Verify that `delete_video_transcript` works as expected.
""" """
query_filter = { query_filter = {
'video_id': 'super-soaker', 'video__edx_video_id': 'super-soaker',
'language_code': 'fr' 'language_code': 'fr'
} }
self.assertEqual(VideoTranscript.objects.filter(**query_filter).count(), 1) self.assertEqual(VideoTranscript.objects.filter(**query_filter).count(), 1)
api.delete_video_transcript(**query_filter) # current transcript path
self.assertFalse(os.path.exists(self.transcript_file)) transcript_path = self.v1_transcript2.transcript.name
api.delete_video_transcript(
video_id=query_filter['video__edx_video_id'],
language_code=query_filter['language_code']
)
# assert that the transcript does not exist on the path anymore.
self.assertFalse(os.path.exists(transcript_path))
self.assertEqual(VideoTranscript.objects.filter(**query_filter).count(), 0) self.assertEqual(VideoTranscript.objects.filter(**query_filter).count(), 0)
......
...@@ -3,7 +3,6 @@ ...@@ -3,7 +3,6 @@
Tests for Video Abstraction Layer views Tests for Video Abstraction Layer views
""" """
import json import json
import unittest
from ddt import data, ddt, unpack from ddt import data, ddt, unpack
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
...@@ -821,13 +820,18 @@ class VideoTranscriptViewTest(APIAuthTestCase): ...@@ -821,13 +820,18 @@ class VideoTranscriptViewTest(APIAuthTestCase):
serialized_data = TranscriptSerializer(VideoTranscript.objects.first()).data serialized_data = TranscriptSerializer(VideoTranscript.objects.first()).data
post_transcript_data['url'] = post_transcript_data.pop('name') post_transcript_data['url'] = post_transcript_data.pop('name')
self.assertEqual(serialized_data, post_transcript_data) self.assertDictEqual(serialized_data, post_transcript_data)
def test_update_existing_transcript(self): def test_update_existing_transcript(self):
""" """
Tests updating existing transcript works as expected. Tests updating existing transcript works as expected.
""" """
VideoTranscript.objects.create(**self.transcript_data) VideoTranscript.objects.create(
video=self.video,
language_code=self.transcript_data['language_code'],
file_format=self.transcript_data['file_format'],
provider=self.transcript_data['provider'],
)
post_transcript_data = dict(self.transcript_data) post_transcript_data = dict(self.transcript_data)
post_transcript_data['name'] = post_transcript_data.pop('transcript') post_transcript_data['name'] = post_transcript_data.pop('transcript')
......
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