Commit 9a878df0 by muhammad-ammar

support video images for course re-run and import/export

parent 23a651d5
...@@ -10,5 +10,6 @@ script: ...@@ -10,5 +10,6 @@ script:
branches: branches:
only: only:
- master - master
- ammar/course-rerun-import-export
after_success: after_success:
coveralls coveralls
...@@ -61,6 +61,7 @@ def create_video(video_data): ...@@ -61,6 +61,7 @@ def create_video(video_data):
file_size: size of the video in bytes file_size: size of the video in bytes
profile: ID of the profile profile: ID of the profile
courses: Courses associated with this video courses: Courses associated with this video
image: poster image file name for a particular course
} }
Raises: Raises:
...@@ -147,7 +148,9 @@ def get_course_video_image_url(course_id, edx_video_id): ...@@ -147,7 +148,9 @@ def get_course_video_image_url(course_id, edx_video_id):
Returns course video image url or None if no image found Returns course video image url or None if no image found
""" """
try: try:
video_image = CourseVideo.objects.get(course_id=course_id, video__edx_video_id=edx_video_id).video_image video_image = CourseVideo.objects.select_related('video_image').get(
course_id=course_id, video__edx_video_id=edx_video_id
).video_image
return video_image.image_url() return video_image.image_url()
except ObjectDoesNotExist: except ObjectDoesNotExist:
return None return None
...@@ -157,6 +160,10 @@ def update_video_image(edx_video_id, course_id, image_data, file_name): ...@@ -157,6 +160,10 @@ def update_video_image(edx_video_id, course_id, image_data, file_name):
""" """
Update video image for an existing video. Update video image for an existing video.
NOTE: If `image_data` is None then `file_name` value will be used as it is, otherwise
a new file name is constructed based on uuid and extension from `file_name` value.
`image_data` will be None in case of course re-run and export.
Arguments: Arguments:
image_data (InMemoryUploadedFile): Image data to be saved for a course video. image_data (InMemoryUploadedFile): Image data to be saved for a course video.
...@@ -167,9 +174,14 @@ def update_video_image(edx_video_id, course_id, image_data, file_name): ...@@ -167,9 +174,14 @@ def update_video_image(edx_video_id, course_id, image_data, file_name):
Raises ValVideoNotFoundError if the CourseVideo cannot be retrieved. Raises ValVideoNotFoundError if the CourseVideo cannot be retrieved.
""" """
try: try:
course_video = CourseVideo.objects.get(course_id=course_id, video__edx_video_id=edx_video_id) course_video = CourseVideo.objects.select_related('video').get(
course_id=course_id, video__edx_video_id=edx_video_id
)
except ObjectDoesNotExist: except ObjectDoesNotExist:
error_message = u'CourseVideo not found for edx_video_id: {0}'.format(edx_video_id) error_message = u'VAL: CourseVideo not found for edx_video_id: {0} and course_id: {1}'.format(
edx_video_id,
course_id
)
raise ValVideoNotFoundError(error_message) raise ValVideoNotFoundError(error_message)
video_image, _ = VideoImage.create_or_update(course_video, file_name, image_data) video_image, _ = VideoImage.create_or_update(course_video, file_name, image_data)
...@@ -476,21 +488,29 @@ def copy_course_videos(source_course_id, destination_course_id): ...@@ -476,21 +488,29 @@ def copy_course_videos(source_course_id, destination_course_id):
if source_course_id == destination_course_id: if source_course_id == destination_course_id:
return return
videos = Video.objects.filter(courses__course_id=unicode(source_course_id)) course_videos = CourseVideo.objects.select_related('video', 'video_image').filter(
course_id=unicode(source_course_id)
)
for video in videos: for course_video in course_videos:
CourseVideo.objects.get_or_create( destination_course_video, __ = CourseVideo.objects.get_or_create(
video=video, video=course_video.video,
course_id=destination_course_id course_id=destination_course_id
) )
if hasattr(course_video, 'video_image'):
VideoImage.create_or_update(
course_video=destination_course_video,
file_name=course_video.video_image.image.name
)
def export_to_xml(edx_video_id): def export_to_xml(edx_video_id, course_id=None):
""" """
Exports data about the given edx_video_id into the given xml object. Exports data about the given edx_video_id into the given xml object.
Args: Args:
edx_video_id (str): The ID of the video to export edx_video_id (str): The ID of the video to export
course_id (str): The ID of the course with which this video is associated
Returns: Returns:
An lxml video_asset element containing export data An lxml video_asset element containing export data
...@@ -498,12 +518,21 @@ def export_to_xml(edx_video_id): ...@@ -498,12 +518,21 @@ def export_to_xml(edx_video_id):
Raises: Raises:
ValVideoNotFoundError: if the video does not exist ValVideoNotFoundError: if the video does not exist
""" """
video_image_name = ''
video = _get_video(edx_video_id) video = _get_video(edx_video_id)
try:
course_video = CourseVideo.objects.select_related('video_image').get(course_id=course_id, video=video)
video_image_name = course_video.video_image.image.name
except ObjectDoesNotExist:
pass
video_el = Element( video_el = Element(
'video_asset', 'video_asset',
attrib={ attrib={
'client_video_id': video.client_video_id, 'client_video_id': video.client_video_id,
'duration': unicode(video.duration), 'duration': unicode(video.duration),
'image': video_image_name
} }
) )
for encoded_video in video.encoded_videos.all(): for encoded_video in video.encoded_videos.all():
...@@ -548,7 +577,12 @@ def import_from_xml(xml, edx_video_id, course_id=None): ...@@ -548,7 +577,12 @@ def import_from_xml(xml, edx_video_id, course_id=None):
course_id, course_id,
) )
if course_id: if course_id:
CourseVideo.get_or_create_with_validation(video=video, course_id=course_id) course_video, __ = CourseVideo.get_or_create_with_validation(video=video, course_id=course_id)
image_file_name = xml.get('image', '').strip()
if image_file_name:
VideoImage.create_or_update(course_video, image_file_name)
return return
except ValidationError as err: except ValidationError as err:
logger.exception(err.message) logger.exception(err.message)
...@@ -563,7 +597,7 @@ def import_from_xml(xml, edx_video_id, course_id=None): ...@@ -563,7 +597,7 @@ def import_from_xml(xml, edx_video_id, course_id=None):
'duration': xml.get('duration'), 'duration': xml.get('duration'),
'status': 'imported', 'status': 'imported',
'encoded_videos': [], 'encoded_videos': [],
'courses': [course_id] if course_id else [], 'courses': [{course_id: xml.get('image')}] if course_id else [],
} }
for encoded_video_el in xml.iterfind('encoded_video'): for encoded_video_el in xml.iterfind('encoded_video'):
profile_name = encoded_video_el.get('profile') profile_name = encoded_video_el.get('profile')
......
...@@ -211,6 +211,10 @@ class VideoImage(TimeStampedModel): ...@@ -211,6 +211,10 @@ class VideoImage(TimeStampedModel):
""" """
Create a VideoImage object for a CourseVideo. Create a VideoImage object for a CourseVideo.
NOTE: If `image_data` is None then `file_name` value will be used as it is, otherwise
a new file name is constructed based on uuid and extension from `file_name` value.
`image_data` will be None in case of course re-run and export.
Arguments: Arguments:
course_video (CourseVideo): CourseVideo instance course_video (CourseVideo): CourseVideo instance
file_name (str): File name of the image file_name (str): File name of the image
...@@ -224,14 +228,12 @@ class VideoImage(TimeStampedModel): ...@@ -224,14 +228,12 @@ class VideoImage(TimeStampedModel):
with closing(image_data) as image_file: with closing(image_data) as image_file:
file_name = '{uuid}{ext}'.format(uuid=uuid4().hex, ext=os.path.splitext(file_name)[1]) file_name = '{uuid}{ext}'.format(uuid=uuid4().hex, ext=os.path.splitext(file_name)[1])
try: try:
course_id = course_video.course_id
edx_video_id = course_video.video.edx_video_id
video_image.image.save(file_name, image_file) video_image.image.save(file_name, image_file)
except Exception: # pylint: disable=broad-except except Exception: # pylint: disable=broad-except
logger.exception( logger.exception(
'VAL: Video Image save failed to storage for course_id [%s] and video_id [%s]', 'VAL: Video Image save failed to storage for course_id [%s] and video_id [%s]',
course_id, course_video.course_id,
edx_video_id course_video.video.edx_video_id
) )
raise raise
else: else:
......
...@@ -182,11 +182,11 @@ class VideoSerializer(serializers.ModelSerializer): ...@@ -182,11 +182,11 @@ class VideoSerializer(serializers.ModelSerializer):
# The CourseSerializer will already have converted the course data # The CourseSerializer will already have converted the course data
# to CourseVideo models, so we can just set the video and save. # to CourseVideo models, so we can just set the video and save.
# Also create VideoImage objects if an image filename is present # Also create VideoImage objects if an image filename is present
for course_video, image in courses: for course_video, image_name in courses:
course_video.video = video course_video.video = video
course_video.save() course_video.save()
if image: if image_name:
VideoImage.create_or_update(course_video, image) VideoImage.create_or_update(course_video, image_name)
return video return video
...@@ -217,10 +217,10 @@ class VideoSerializer(serializers.ModelSerializer): ...@@ -217,10 +217,10 @@ class VideoSerializer(serializers.ModelSerializer):
# NOTE: for backwards compatibility with the DRF v2 behavior, # NOTE: for backwards compatibility with the DRF v2 behavior,
# we do NOT delete existing course videos during the update. # we do NOT delete existing course videos during the update.
# Also update VideoImage objects if an image filename is present # Also update VideoImage objects if an image filename is present
for course_video, image in validated_data.get("courses", []): for course_video, image_name in validated_data.get("courses", []):
course_video.video = instance course_video.video = instance
course_video.save() course_video.save()
if image: if image_name:
VideoImage.create_or_update(course_video, image) VideoImage.create_or_update(course_video, image_name)
return instance return instance
...@@ -12,7 +12,7 @@ from django.test import TestCase ...@@ -12,7 +12,7 @@ from django.test import TestCase
from django.db import DatabaseError from django.db import DatabaseError
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from rest_framework import status from rest_framework import status
from ddt import ddt, data from ddt import ddt, data, unpack
from edxval.models import Profile, Video, EncodedVideo, CourseVideo, VideoImage from edxval.models import Profile, Video, EncodedVideo, CourseVideo, VideoImage
from edxval import api as api from edxval import api as api
...@@ -793,9 +793,11 @@ class TestCopyCourse(TestCase): ...@@ -793,9 +793,11 @@ class TestCopyCourse(TestCase):
Creates a course with 2 videos and a course with 1 video Creates a course with 2 videos and a course with 1 video
""" """
self.course_id = 'test-course' self.course_id = 'test-course'
self.image_name1 = 'image.jpg'
# 1st video # 1st video
self.video1 = Video.objects.create(**constants.VIDEO_DICT_FISH) self.video1 = Video.objects.create(**constants.VIDEO_DICT_FISH)
CourseVideo.objects.create(video=self.video1, course_id=self.course_id) self.course_video1 = CourseVideo.objects.create(video=self.video1, course_id=self.course_id)
VideoImage.create_or_update(self.course_video1, self.image_name1)
# 2nd video # 2nd video
self.video2 = Video.objects.create(**constants.VIDEO_DICT_STAR) self.video2 = Video.objects.create(**constants.VIDEO_DICT_STAR)
CourseVideo.objects.create(video=self.video2, course_id=self.course_id) CourseVideo.objects.create(video=self.video2, course_id=self.course_id)
...@@ -806,10 +808,15 @@ class TestCopyCourse(TestCase): ...@@ -806,10 +808,15 @@ class TestCopyCourse(TestCase):
CourseVideo.objects.create(video=self.video3, course_id=self.course_id2) CourseVideo.objects.create(video=self.video3, course_id=self.course_id2)
def test_successful_copy(self): def test_successful_copy(self):
"""Tests a successful copy course""" """
api.copy_course_videos('test-course', 'course-copy1') Tests a successful copy course
original_videos = Video.objects.filter(courses__course_id='test-course') """
copied_videos = Video.objects.filter(courses__course_id='course-copy1') destination_course_id = 'course-copy1'
api.copy_course_videos(self.course_id, destination_course_id)
original_videos = Video.objects.filter(courses__course_id=self.course_id)
copied_videos = Video.objects.filter(courses__course_id=destination_course_id)
course_video_with_image = CourseVideo.objects.get(video=self.video1, course_id=destination_course_id)
course_video_without_image = CourseVideo.objects.get(video=self.video2, course_id=destination_course_id)
self.assertEqual(len(original_videos), 2) self.assertEqual(len(original_videos), 2)
self.assertEqual( self.assertEqual(
...@@ -817,6 +824,8 @@ class TestCopyCourse(TestCase): ...@@ -817,6 +824,8 @@ class TestCopyCourse(TestCase):
{constants.VIDEO_DICT_FISH["edx_video_id"], constants.VIDEO_DICT_STAR["edx_video_id"]} {constants.VIDEO_DICT_FISH["edx_video_id"], constants.VIDEO_DICT_STAR["edx_video_id"]}
) )
self.assertTrue(set(original_videos) == set(copied_videos)) self.assertTrue(set(original_videos) == set(copied_videos))
self.assertEqual(course_video_with_image.video_image.image.name, self.image_name1)
self.assertFalse(hasattr(course_video_without_image, 'video_image'))
def test_same_course_ids(self): def test_same_course_ids(self):
""" """
...@@ -853,6 +862,7 @@ class TestCopyCourse(TestCase): ...@@ -853,6 +862,7 @@ class TestCopyCourse(TestCase):
self.assertTrue(set(copied_videos) == set(original_videos)) self.assertTrue(set(copied_videos) == set(original_videos))
@ddt
class ExportTest(TestCase): class ExportTest(TestCase):
"""Tests export_to_xml""" """Tests export_to_xml"""
def setUp(self): def setUp(self):
...@@ -861,6 +871,9 @@ class ExportTest(TestCase): ...@@ -861,6 +871,9 @@ class ExportTest(TestCase):
hls_profile = Profile.objects.get(profile_name=constants.PROFILE_HLS) hls_profile = Profile.objects.get(profile_name=constants.PROFILE_HLS)
Video.objects.create(**constants.VIDEO_DICT_STAR) Video.objects.create(**constants.VIDEO_DICT_STAR)
video = Video.objects.create(**constants.VIDEO_DICT_FISH) video = Video.objects.create(**constants.VIDEO_DICT_FISH)
course_video = CourseVideo.objects.create(video=video, course_id='test-course')
VideoImage.create_or_update(course_video, 'image.jpg')
EncodedVideo.objects.create( EncodedVideo.objects.create(
video=video, video=video,
profile=mobile_profile, profile=mobile_profile,
...@@ -899,23 +912,29 @@ class ExportTest(TestCase): ...@@ -899,23 +912,29 @@ class ExportTest(TestCase):
def test_no_encodings(self): def test_no_encodings(self):
expected = self.parse_xml(""" expected = self.parse_xml("""
<video_asset client_video_id="TWINKLE TWINKLE" duration="122.0"/> <video_asset client_video_id="TWINKLE TWINKLE" duration="122.0" image=""/>
""") """)
self.assert_xml_equal( self.assert_xml_equal(
api.export_to_xml(constants.VIDEO_DICT_STAR["edx_video_id"]), api.export_to_xml(constants.VIDEO_DICT_STAR["edx_video_id"]),
expected expected
) )
def test_basic(self): @data(
{'course_id': None, 'image':''},
{'course_id': 'test-course', 'image':'image.jpg'},
)
@unpack
def test_basic(self, course_id, image):
expected = self.parse_xml(""" expected = self.parse_xml("""
<video_asset client_video_id="Shallow Swordfish" duration="122.0"> <video_asset client_video_id="Shallow Swordfish" duration="122.0" image="{image}">
<encoded_video url="http://www.meowmix.com" file_size="11" bitrate="22" profile="mobile"/> <encoded_video url="http://www.meowmix.com" file_size="11" bitrate="22" profile="mobile"/>
<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"/>
</video_asset> </video_asset>
""") """.format(image=image))
self.assert_xml_equal( self.assert_xml_equal(
api.export_to_xml(constants.VIDEO_DICT_FISH["edx_video_id"]), api.export_to_xml(constants.VIDEO_DICT_FISH['edx_video_id'], course_id),
expected expected
) )
...@@ -928,6 +947,7 @@ class ExportTest(TestCase): ...@@ -928,6 +947,7 @@ class ExportTest(TestCase):
class ImportTest(TestCase): class ImportTest(TestCase):
"""Tests import_from_xml""" """Tests import_from_xml"""
def setUp(self): def setUp(self):
self.image_name = 'image.jpg'
mobile_profile = Profile.objects.create(profile_name=constants.PROFILE_MOBILE) mobile_profile = Profile.objects.create(profile_name=constants.PROFILE_MOBILE)
Profile.objects.create(profile_name=constants.PROFILE_DESKTOP) Profile.objects.create(profile_name=constants.PROFILE_DESKTOP)
video = Video.objects.create(**constants.VIDEO_DICT_FISH) video = Video.objects.create(**constants.VIDEO_DICT_FISH)
...@@ -938,24 +958,28 @@ class ImportTest(TestCase): ...@@ -938,24 +958,28 @@ class ImportTest(TestCase):
) )
CourseVideo.objects.create(video=video, course_id='existing_course_id') CourseVideo.objects.create(video=video, course_id='existing_course_id')
def make_import_xml(self, video_dict, encoded_video_dicts=None): def make_import_xml(self, video_dict, encoded_video_dicts=None, image=None):
ret = etree.Element( import_xml = etree.Element(
"video_asset", "video_asset",
attrib={ attrib={
key: unicode(video_dict[key]) key: unicode(video_dict[key])
for key in ["client_video_id", "duration"] for key in ["client_video_id", "duration"]
} }
) )
if image:
import_xml.attrib['image'] = image
for encoding_dict in (encoded_video_dicts or []): for encoding_dict in (encoded_video_dicts or []):
etree.SubElement( etree.SubElement(
ret, import_xml,
"encoded_video", "encoded_video",
attrib={ attrib={
key: unicode(val) key: unicode(val)
for key, val in encoding_dict.items() for key, val in encoding_dict.items()
} }
) )
return ret return import_xml
def assert_obj_matches_dict_for_keys(self, obj, dict_, keys): def assert_obj_matches_dict_for_keys(self, obj, dict_, keys):
for key in keys: for key in keys:
...@@ -986,8 +1010,10 @@ class ImportTest(TestCase): ...@@ -986,8 +1010,10 @@ class ImportTest(TestCase):
xml = self.make_import_xml( xml = self.make_import_xml(
video_dict=constants.VIDEO_DICT_STAR, video_dict=constants.VIDEO_DICT_STAR,
encoded_video_dicts=[constants.ENCODED_VIDEO_DICT_STAR, constants.ENCODED_VIDEO_DICT_FISH_HLS] encoded_video_dicts=[constants.ENCODED_VIDEO_DICT_STAR, constants.ENCODED_VIDEO_DICT_FISH_HLS],
image=self.image_name
) )
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)
video = Video.objects.get(edx_video_id=constants.VIDEO_DICT_STAR["edx_video_id"]) video = Video.objects.get(edx_video_id=constants.VIDEO_DICT_STAR["edx_video_id"])
...@@ -1000,7 +1026,8 @@ class ImportTest(TestCase): ...@@ -1000,7 +1026,8 @@ class ImportTest(TestCase):
video.encoded_videos.get(profile__profile_name=constants.PROFILE_HLS), video.encoded_videos.get(profile__profile_name=constants.PROFILE_HLS),
constants.ENCODED_VIDEO_DICT_FISH_HLS constants.ENCODED_VIDEO_DICT_FISH_HLS
) )
video.courses.get(course_id=new_course_id) course_video = video.courses.get(course_id=new_course_id)
self.assertTrue(course_video.video_image.image.name, self.image_name)
def test_new_video_minimal(self): def test_new_video_minimal(self):
edx_video_id = "test_edx_video_id" edx_video_id = "test_edx_video_id"
...@@ -1037,7 +1064,8 @@ class ImportTest(TestCase): ...@@ -1037,7 +1064,8 @@ class ImportTest(TestCase):
"bitrate": 1597804, "bitrate": 1597804,
"profile": "mobile", "profile": "mobile",
}, },
] ],
image=self.image_name
) )
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)
...@@ -1051,6 +1079,8 @@ class ImportTest(TestCase): ...@@ -1051,6 +1079,8 @@ class ImportTest(TestCase):
video.encoded_videos.filter(profile__profile_name=constants.PROFILE_DESKTOP).exists() video.encoded_videos.filter(profile__profile_name=constants.PROFILE_DESKTOP).exists()
) )
self.assertTrue(video.courses.filter(course_id=course_id).exists()) self.assertTrue(video.courses.filter(course_id=course_id).exists())
course_video = video.courses.get(course_id=course_id)
self.assertTrue(course_video.video_image.image.name, self.image_name)
def test_existing_video_with_invalid_course_id(self): def test_existing_video_with_invalid_course_id(self):
...@@ -1197,12 +1227,12 @@ class VideoStatusUpdateTest(TestCase): ...@@ -1197,12 +1227,12 @@ class VideoStatusUpdateTest(TestCase):
class CourseVideoImageTest(TestCase): class CourseVideoImageTest(TestCase):
""" """
Tests to check course video image related functions works correctly Tests to check course video image related functions works correctly.
""" """
def setUp(self): def setUp(self):
""" """
Creates video objects for courses Creates video objects for courses.
""" """
self.course_id = 'test-course' self.course_id = 'test-course'
self.course_id2 = 'test-course2' self.course_id2 = 'test-course2'
...@@ -1243,6 +1273,22 @@ class CourseVideoImageTest(TestCase): ...@@ -1243,6 +1273,22 @@ class CourseVideoImageTest(TestCase):
image_url = api.get_course_video_image_url(self.course_id, self.edx_video_id) image_url = api.get_course_video_image_url(self.course_id, self.edx_video_id)
self.assertIsNone(image_url) self.assertIsNone(image_url)
def test_num_queries_update_video_image(self):
"""
Test number of queries executed to upload a course video image.
"""
with self.assertNumQueries(4):
api.update_video_image(
self.edx_video_id, self.course_id, ImageFile(open(self.image_path1)), 'image.jpg'
)
def test_num_queries_get_course_video_image_url(self):
"""
Test number of queries executed to get a course video image url.
"""
with self.assertNumQueries(1):
api.get_course_video_image_url(self.course_id, self.edx_video_id)
def test_get_videos_for_course(self): def test_get_videos_for_course(self):
""" """
Verify that `get_videos_for_course` api function has correct course_video_image_url. Verify that `get_videos_for_course` api function has correct course_video_image_url.
...@@ -1262,7 +1308,7 @@ class CourseVideoImageTest(TestCase): ...@@ -1262,7 +1308,7 @@ class CourseVideoImageTest(TestCase):
@patch('edxval.models.logger') @patch('edxval.models.logger')
def test_create_or_update_logging(self, mock_logger): def test_create_or_update_logging(self, mock_logger):
""" """
Tests correct message is logged when save to storge is failed in `create_or_update` Tests correct message is logged when save to storge is failed in `create_or_update`.
""" """
with self.assertRaises(Exception) as save_exception: # pylint: disable=unused-variable with self.assertRaises(Exception) as save_exception: # pylint: disable=unused-variable
VideoImage.create_or_update(self.course_video, 'test.jpg', open(self.image_path2)) VideoImage.create_or_update(self.course_video, 'test.jpg', open(self.image_path2))
...@@ -1272,3 +1318,20 @@ class CourseVideoImageTest(TestCase): ...@@ -1272,3 +1318,20 @@ class CourseVideoImageTest(TestCase):
self.course_video.course_id, self.course_video.course_id,
self.course_video.video.edx_video_id self.course_video.video.edx_video_id
) )
def test_update_video_image_exception(self):
"""
Tests exception message when we hit by an exception in `update_video_image`.
"""
does_not_course_id = 'does_not_exist'
with self.assertRaises(Exception) as get_exception:
api.update_video_image(self.edx_video_id, does_not_course_id, open(self.image_path2), 'test.jpg')
self.assertEqual(
get_exception.exception.message,
u'VAL: CourseVideo not found for edx_video_id: {0} and course_id: {1}'.format(
self.edx_video_id,
does_not_course_id
)
)
...@@ -39,7 +39,7 @@ def load_requirements(*requirements_paths): ...@@ -39,7 +39,7 @@ def load_requirements(*requirements_paths):
setup( setup(
name='edxval', name='edxval',
version='0.0.13', version='0.0.15',
author='edX', author='edX',
url='http://github.com/edx/edx-val', url='http://github.com/edx/edx-val',
description='edx-val', description='edx-val',
......
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