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
...@@ -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