Commit f1401674 by muhammad-ammar

test ospr 91

parent a6c0d3a0
language: python language: python
python: python:
- '2.7' - '2.7'
- '3.6'
env: env:
- TOXENV=django18 - TOXENV=django18
- TOXENV=django110 - TOXENV=django110
......
...@@ -10,21 +10,21 @@ Retrieve all profiles for a video with `edx_video_id`="example" ...@@ -10,21 +10,21 @@ Retrieve all profiles for a video with `edx_video_id`="example"
Returns (dict): Returns (dict):
{ {
'url' : '/edxval/videos/example', 'url' : '/edxval/videos/example',
'edx_video_id': u'example', 'edx_video_id': 'example',
'duration': 111.0, 'duration': 111.0,
'client_video_id': u'The example video', 'client_video_id': 'The example video',
'encoded_videos': [ 'encoded_videos': [
{ {
'url': u'http://www.example.com/example_mobile_video.mp4', 'url': 'http://www.example.com/example_mobile_video.mp4',
'file_size': 25556, 'file_size': 25556,
'bitrate': 9600, 'bitrate': 9600,
'profile': u'mobile' 'profile': 'mobile'
}, },
{ {
'url': u'http://www.example.com/example_desktop_video.mp4', 'url': 'http://www.example.com/example_desktop_video.mp4',
'file_size': 43096734, 'file_size': 43096734,
'bitrate': 64000, 'bitrate': 64000,
'profile': u'desktop' 'profile': 'desktop'
} }
] ]
} }
......
""" """
Admin file for django app edxval. Admin file for django app edxval.
""" """
from __future__ import unicode_literals
from django.contrib import admin from django.contrib import admin
from .models import (CourseVideo, EncodedVideo, Profile, TranscriptPreference, from .models import (CourseVideo, EncodedVideo, Profile, TranscriptPreference,
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
""" """
The internal API for VAL. The internal API for VAL.
""" """
from __future__ import unicode_literals
import logging import logging
from enum import Enum from enum import Enum
...@@ -10,14 +11,26 @@ from django.core.exceptions import ObjectDoesNotExist, ValidationError ...@@ -10,14 +11,26 @@ from django.core.exceptions import ObjectDoesNotExist, ValidationError
from lxml import etree from lxml import etree
from lxml.etree import Element, SubElement from lxml.etree import Element, SubElement
from edxval.exceptions import (InvalidTranscriptFormat, from edxval.exceptions import (
InvalidTranscriptProvider, ValCannotCreateError, InvalidTranscriptFormat,
ValCannotUpdateError, ValInternalError, InvalidTranscriptProvider,
ValVideoNotFoundError) ValCannotCreateError,
from edxval.models import (CourseVideo, EncodedVideo, Profile, ValCannotUpdateError,
TranscriptFormat, TranscriptPreference, ValInternalError,
TranscriptProviderType, Video, VideoImage, ValVideoNotFoundError,
VideoTranscript, ThirdPartyTranscriptCredentialsState) )
from edxval.models import (
CourseVideo,
EncodedVideo,
Profile,
TranscriptFormat,
TranscriptPreference,
TranscriptProviderType,
Video,
VideoImage,
VideoTranscript,
ThirdPartyTranscriptCredentialsState,
)
from edxval.serializers import TranscriptPreferenceSerializer, TranscriptSerializer, VideoSerializer from edxval.serializers import TranscriptPreferenceSerializer, TranscriptSerializer, VideoSerializer
from edxval.utils import THIRD_PARTY_TRANSCRIPTION_PLANS from edxval.utils import THIRD_PARTY_TRANSCRIPTION_PLANS
...@@ -423,7 +436,7 @@ def update_video_image(edx_video_id, course_id, image_data, file_name): ...@@ -423,7 +436,7 @@ def update_video_image(edx_video_id, course_id, image_data, file_name):
course_id=course_id, video__edx_video_id=edx_video_id course_id=course_id, video__edx_video_id=edx_video_id
) )
except ObjectDoesNotExist: except ObjectDoesNotExist:
error_message = u'VAL: CourseVideo not found for edx_video_id: {0} and course_id: {1}'.format( error_message = 'VAL: CourseVideo not found for edx_video_id: {0} and course_id: {1}'.format(
edx_video_id, edx_video_id,
course_id course_id
) )
...@@ -503,15 +516,15 @@ def get_video_info(edx_video_id): ...@@ -503,15 +516,15 @@ def get_video_info(edx_video_id):
Returns (dict): Returns (dict):
{ {
'url' : '/edxval/videos/example', 'url' : '/edxval/videos/example',
'edx_video_id': u'example', 'edx_video_id': 'example',
'duration': 111.0, 'duration': 111.0,
'client_video_id': u'The example video', 'client_video_id': 'The example video',
'encoded_videos': [ 'encoded_videos': [
{ {
'url': u'http://www.example.com', 'url': 'http://www.example.com',
'file_size': 25556, 'file_size': 25556,
'bitrate': 9600, 'bitrate': 9600,
'profile': u'mobile' 'profile': 'mobile'
} }
] ]
} }
...@@ -590,7 +603,7 @@ def get_videos_for_course(course_id, sort_field=None, sort_dir=SortDirection.asc ...@@ -590,7 +603,7 @@ def get_videos_for_course(course_id, sort_field=None, sort_dir=SortDirection.asc
total order. total order.
""" """
return _get_videos_for_filter( return _get_videos_for_filter(
{'courses__course_id': unicode(course_id), 'courses__is_hidden': False}, {'courses__course_id': course_id, 'courses__is_hidden': False},
sort_field, sort_field,
sort_dir, sort_dir,
) )
...@@ -658,28 +671,28 @@ def get_video_info_for_course_and_profiles(course_id, profiles): ...@@ -658,28 +671,28 @@ def get_video_info_for_course_and_profiles(course_id, profiles):
Example: Example:
Given two videos with two profiles each in course_id 'test_course': Given two videos with two profiles each in course_id 'test_course':
{ {
u'edx_video_id_1': { 'edx_video_id_1': {
u'duration: 1111, 'duration: 1111,
u'profiles': { 'profiles': {
u'mobile': { 'mobile': {
'url': u'http: //www.example.com/meow', 'url': 'http: //www.example.com/meow',
'file_size': 2222 'file_size': 2222
}, },
u'desktop': { 'desktop': {
'url': u'http: //www.example.com/woof', 'url': 'http: //www.example.com/woof',
'file_size': 4444 'file_size': 4444
} }
} }
}, },
u'edx_video_id_2': { 'edx_video_id_2': {
u'duration: 2222, 'duration: 2222,
u'profiles': { 'profiles': {
u'mobile': { 'mobile': {
'url': u'http: //www.example.com/roar', 'url': 'http: //www.example.com/roar',
'file_size': 6666 'file_size': 6666
}, },
u'desktop': { 'desktop': {
'url': u'http: //www.example.com/bzzz', 'url': 'http: //www.example.com/bzzz',
'file_size': 8888 'file_size': 8888
} }
} }
...@@ -687,7 +700,6 @@ def get_video_info_for_course_and_profiles(course_id, profiles): ...@@ -687,7 +700,6 @@ def get_video_info_for_course_and_profiles(course_id, profiles):
} }
""" """
# In case someone passes in a key (VAL doesn't really understand opaque keys) # In case someone passes in a key (VAL doesn't really understand opaque keys)
course_id = unicode(course_id)
try: try:
encoded_videos = EncodedVideo.objects.filter( encoded_videos = EncodedVideo.objects.filter(
profile__profile_name__in=profiles, profile__profile_name__in=profiles,
...@@ -729,7 +741,7 @@ def copy_course_videos(source_course_id, destination_course_id): ...@@ -729,7 +741,7 @@ def copy_course_videos(source_course_id, destination_course_id):
return return
course_videos = CourseVideo.objects.select_related('video', 'video_image').filter( course_videos = CourseVideo.objects.select_related('video', 'video_image').filter(
course_id=unicode(source_course_id) course_id=str(source_course_id)
) )
for course_video in course_videos: for course_video in course_videos:
...@@ -785,7 +797,7 @@ def export_to_xml(video_ids, course_id=None, external=False): ...@@ -785,7 +797,7 @@ def export_to_xml(video_ids, course_id=None, external=False):
'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': str(video.duration),
'image': video_image_name 'image': video_image_name
} }
) )
...@@ -794,7 +806,7 @@ def export_to_xml(video_ids, course_id=None, external=False): ...@@ -794,7 +806,7 @@ def export_to_xml(video_ids, course_id=None, external=False):
video_el, video_el,
'encoded_video', 'encoded_video',
{ {
name: unicode(getattr(encoded_video, name)) name: str(getattr(encoded_video, name))
for name in ['profile', 'url', 'file_size', 'bitrate'] for name in ['profile', 'url', 'file_size', 'bitrate']
} }
) )
...@@ -879,7 +891,7 @@ def import_from_xml(xml, edx_video_id, course_id=None): ...@@ -879,7 +891,7 @@ def import_from_xml(xml, edx_video_id, course_id=None):
return return
except ValidationError as err: except ValidationError as err:
logger.exception(err.message) logger.exception(err)
raise ValCannotCreateError(err.message_dict) raise ValCannotCreateError(err.message_dict)
except Video.DoesNotExist: except Video.DoesNotExist:
pass pass
......
""" """
VAL Exceptions. VAL Exceptions.
""" """
from __future__ import unicode_literals
class ValError(Exception): class ValError(Exception):
""" """
......
...@@ -35,7 +35,7 @@ class Migration(migrations.Migration): ...@@ -35,7 +35,7 @@ class Migration(migrations.Migration):
name='Profile', name='Profile',
fields=[ fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('profile_name', models.CharField(unique=True, max_length=50, validators=[django.core.validators.RegexValidator(regex=b'^[a-zA-Z0-9\\-_]*$', message=b'profile_name has invalid characters', code=b'invalid profile_name')])), ('profile_name', models.CharField(unique=True, max_length=50, validators=[django.core.validators.RegexValidator(regex='^[a-zA-Z0-9\\-_]*$', message=b'profile_name has invalid characters', code=b'invalid profile_name')])),
], ],
), ),
migrations.CreateModel( migrations.CreateModel(
...@@ -54,7 +54,7 @@ class Migration(migrations.Migration): ...@@ -54,7 +54,7 @@ class Migration(migrations.Migration):
fields=[ fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('created', models.DateTimeField(auto_now_add=True)), ('created', models.DateTimeField(auto_now_add=True)),
('edx_video_id', models.CharField(unique=True, max_length=100, validators=[django.core.validators.RegexValidator(regex=b'^[a-zA-Z0-9\\-_]*$', message=b'edx_video_id has invalid characters', code=b'invalid edx_video_id')])), ('edx_video_id', models.CharField(unique=True, max_length=100, validators=[django.core.validators.RegexValidator(regex='^[a-zA-Z0-9\\-_]*$', message=b'edx_video_id has invalid characters', code=b'invalid edx_video_id')])),
('client_video_id', models.CharField(db_index=True, max_length=255, blank=True)), ('client_video_id', models.CharField(db_index=True, max_length=255, blank=True)),
('duration', models.FloatField(validators=[django.core.validators.MinValueValidator(0)])), ('duration', models.FloatField(validators=[django.core.validators.MinValueValidator(0)])),
('status', models.CharField(max_length=255, db_index=True)), ('status', models.CharField(max_length=255, db_index=True)),
......
...@@ -11,6 +11,7 @@ themselves. After these are resolved, errors such as a negative file_size or ...@@ -11,6 +11,7 @@ themselves. After these are resolved, errors such as a negative file_size or
invalid profile_name will be returned. invalid profile_name will be returned.
""" """
from __future__ import unicode_literals
import json import json
import logging import logging
import os import os
...@@ -22,7 +23,7 @@ from django.core.urlresolvers import reverse ...@@ -22,7 +23,7 @@ from django.core.urlresolvers import reverse
from django.core.validators import MinValueValidator, RegexValidator from django.core.validators import MinValueValidator, RegexValidator
from django.db import models from django.db import models
from django.dispatch import receiver from django.dispatch import receiver
from django.utils.six import python_2_unicode_compatible from django.utils.six import python_2_unicode_compatible, string_types
from model_utils.models import TimeStampedModel from model_utils.models import TimeStampedModel
from edxval.utils import (get_video_image_storage, from edxval.utils import (get_video_image_storage,
...@@ -219,7 +220,7 @@ class ListField(models.TextField): ...@@ -219,7 +220,7 @@ class ListField(models.TextField):
Converts a list to its json representation to store in database as text. Converts a list to its json representation to store in database as text.
""" """
if value and not isinstance(value, list): if value and not isinstance(value, list):
raise ValidationError(u'ListField value {} is not a list.'.format(value)) raise ValidationError('ListField value {} is not a list.'.format(value))
return json.dumps(self.validate_list(value) or []) return json.dumps(self.validate_list(value) or [])
def from_db_value(self, value, expression, connection, context): def from_db_value(self, value, expression, connection, context):
...@@ -247,7 +248,7 @@ class ListField(models.TextField): ...@@ -247,7 +248,7 @@ class ListField(models.TextField):
self.validate_list(py_list) self.validate_list(py_list)
except (ValueError, TypeError): except (ValueError, TypeError):
raise ValidationError(u'Must be a valid list of strings.') raise ValidationError('Must be a valid list of strings.')
return py_list return py_list
...@@ -265,12 +266,10 @@ class ListField(models.TextField): ...@@ -265,12 +266,10 @@ class ListField(models.TextField):
ValidationError ValidationError
""" """
if len(value) > self.max_items: if len(value) > self.max_items:
raise ValidationError( raise ValidationError('list must not contain more than {max_items} items.'.format(max_items=self.max_items))
u'list must not contain more than {max_items} items.'.format(max_items=self.max_items)
)
if all(isinstance(item, basestring) for item in value) is False: if all(isinstance(item, string_types) for item in value) is False:
raise ValidationError(u'list must only contain strings.') raise ValidationError('list must only contain strings.')
return value return value
......
...@@ -4,6 +4,7 @@ Serializers for Video Abstraction Layer ...@@ -4,6 +4,7 @@ Serializers for Video Abstraction Layer
Serialization is usually sent through the VideoSerializer which uses the Serialization is usually sent through the VideoSerializer which uses the
EncodedVideoSerializer which uses the profile_name as it's profile field. EncodedVideoSerializer which uses the profile_name as it's profile field.
""" """
from __future__ import unicode_literals
from rest_framework import serializers from rest_framework import serializers
from rest_framework.fields import DateTimeField, IntegerField from rest_framework.fields import DateTimeField, IntegerField
......
""" """
Settings file for django app edxval. Settings file for django app edxval.
""" """
from __future__ import unicode_literals
DEBUG = True DEBUG = True
TEMPLATE_DEBUG = DEBUG TEMPLATE_DEBUG = DEBUG
......
# pylint: disable=E1103, W0105 # pylint: disable=E1103, W0105
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals
""" """
Constants used for tests. Constants used for tests.
""" """
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
""" """
Tests for the API for Video Abstraction Layer Tests for the API for Video Abstraction Layer
""" """
from __future__ import unicode_literals
import json import json
import mock import mock
...@@ -36,8 +37,7 @@ FILE_DATA = """ ...@@ -36,8 +37,7 @@ FILE_DATA = """
I am overwatch. I am overwatch.
2 2
00:00:16,500 --> 00:00:18,600 00:00:16,500
可以用“我不太懂艺术 但我知道我喜欢什么”做比喻.
""" """
...@@ -215,7 +215,7 @@ class CreateProfileTest(TestCase): ...@@ -215,7 +215,7 @@ class CreateProfileTest(TestCase):
""" """
api.create_profile(constants.PROFILE_DESKTOP) api.create_profile(constants.PROFILE_DESKTOP)
profiles = list(Profile.objects.all()) profiles = list(Profile.objects.all())
profile_names = [unicode(profile) for profile in profiles] profile_names = [str(profile) for profile in profiles]
self.assertEqual(len(profiles), 7) self.assertEqual(len(profiles), 7)
self.assertIn( self.assertIn(
constants.PROFILE_DESKTOP, constants.PROFILE_DESKTOP,
...@@ -290,12 +290,12 @@ class GetVideoInfoTest(TestCase): ...@@ -290,12 +290,12 @@ class GetVideoInfoTest(TestCase):
with self.assertRaises(api.ValVideoNotFoundError): with self.assertRaises(api.ValVideoNotFoundError):
api.get_video_info("") api.get_video_info("")
def test_unicode_input(self): def test_str_input(self):
""" """
Tests if unicode inputs are handled correctly Tests if unicode inputs are handled correctly
""" """
with self.assertRaises(api.ValVideoNotFoundError): with self.assertRaises(api.ValVideoNotFoundError):
api.get_video_info(u"๓ﻉѻฝ๓ٱซ") api.get_video_info("๓ﻉѻฝ๓ٱซ")
@mock.patch.object(Video, '__init__') @mock.patch.object(Video, '__init__')
def test_force_database_error(self, mock_get): def test_force_database_error(self, mock_get):
...@@ -353,9 +353,9 @@ class GetUrlsForProfileTest(TestCase): ...@@ -353,9 +353,9 @@ class GetUrlsForProfileTest(TestCase):
edx_video_id = constants.VIDEO_DICT_FISH['edx_video_id'] edx_video_id = constants.VIDEO_DICT_FISH['edx_video_id']
urls = api.get_urls_for_profiles(edx_video_id, profiles) urls = api.get_urls_for_profiles(edx_video_id, profiles)
self.assertEqual(len(urls), 3) self.assertEqual(len(urls), 3)
self.assertEqual(urls["mobile"], u'http://www.meowmix.com') self.assertEqual(urls["mobile"], 'http://www.meowmix.com')
self.assertEqual(urls["desktop"], u'http://www.meowmagic.com') self.assertEqual(urls["desktop"], 'http://www.meowmagic.com')
self.assertEqual(urls["hls"], u'https://www.tmnt.com/tmnt101.m3u8') self.assertEqual(urls["hls"], 'https://www.tmnt.com/tmnt101.m3u8')
def test_get_urls_for_profiles_no_video(self): def test_get_urls_for_profiles_no_video(self):
""" """
...@@ -382,7 +382,7 @@ class GetUrlsForProfileTest(TestCase): ...@@ -382,7 +382,7 @@ class GetUrlsForProfileTest(TestCase):
profile = "mobile" profile = "mobile"
edx_video_id = constants.VIDEO_DICT_FISH['edx_video_id'] edx_video_id = constants.VIDEO_DICT_FISH['edx_video_id']
url = api.get_url_for_profile(edx_video_id, profile) url = api.get_url_for_profile(edx_video_id, profile)
self.assertEqual(url, u'http://www.meowmix.com') self.assertEqual(url, 'http://www.meowmix.com')
class GetVideoForCourseProfiles(TestCase): class GetVideoForCourseProfiles(TestCase):
...@@ -463,7 +463,7 @@ class GetVideoForCourseProfiles(TestCase): ...@@ -463,7 +463,7 @@ class GetVideoForCourseProfiles(TestCase):
"url": encoding["url"], "url": encoding["url"],
"file_size": encoding["file_size"], "file_size": encoding["file_size"],
} }
for (profile_name, encoding) in encoding_dict.iteritems() for (profile_name, encoding) in encoding_dict.items()
} }
} }
} }
...@@ -1048,7 +1048,7 @@ class ImportTest(TestCase): ...@@ -1048,7 +1048,7 @@ class ImportTest(TestCase):
import_xml = etree.Element( import_xml = etree.Element(
"video_asset", "video_asset",
attrib={ attrib={
key: unicode(video_dict[key]) key: str(video_dict[key])
for key in ["client_video_id", "duration"] for key in ["client_video_id", "duration"]
} }
) )
...@@ -1061,7 +1061,7 @@ class ImportTest(TestCase): ...@@ -1061,7 +1061,7 @@ class ImportTest(TestCase):
import_xml, import_xml,
"encoded_video", "encoded_video",
attrib={ attrib={
key: unicode(val) key: str(val)
for key, val in encoding_dict.items() for key, val in encoding_dict.items()
} }
) )
...@@ -1341,7 +1341,7 @@ class ImportTest(TestCase): ...@@ -1341,7 +1341,7 @@ class ImportTest(TestCase):
mock_logger.warn.assert_called_with( mock_logger.warn.assert_called_with(
"VAL: Required attributes are missing from xml, xml=[%s]", "VAL: Required attributes are missing from xml, xml=[%s]",
transcript_xml b'<transcript file_name="wow.srt" language_code="en" file_format="srt" provider="Cielo24"/>'
) )
self.assert_transcripts(video_id, [self.transcript_data2]) self.assert_transcripts(video_id, [self.transcript_data2])
...@@ -1447,10 +1447,10 @@ class CourseVideoImageTest(TestCase): ...@@ -1447,10 +1447,10 @@ class CourseVideoImageTest(TestCase):
self.image_path1 = 'edxval/tests/data/image.jpg' self.image_path1 = 'edxval/tests/data/image.jpg'
self.image_path2 = 'edxval/tests/data/edx.jpg' self.image_path2 = 'edxval/tests/data/edx.jpg'
self.image_url = api.update_video_image( self.image_url = api.update_video_image(
self.edx_video_id, self.course_id, ImageFile(open(self.image_path1)), 'image.jpg' self.edx_video_id, self.course_id, ImageFile(open(self.image_path1, 'rb')), 'image.jpg'
) )
self.image_url2 = api.update_video_image( self.image_url2 = api.update_video_image(
self.edx_video_id, self.course_id2, ImageFile(open(self.image_path2)), 'image.jpg' self.edx_video_id, self.course_id2, ImageFile(open(self.image_path2, 'rb')), 'image.jpg'
) )
def test_update_video_image(self): def test_update_video_image(self):
...@@ -1459,8 +1459,8 @@ class CourseVideoImageTest(TestCase): ...@@ -1459,8 +1459,8 @@ class CourseVideoImageTest(TestCase):
""" """
self.assertEqual(self.course_video.video_image.image.name, self.image_url) self.assertEqual(self.course_video.video_image.image.name, self.image_url)
self.assertEqual(self.course_video2.video_image.image.name, self.image_url2) self.assertEqual(self.course_video2.video_image.image.name, self.image_url2)
self.assertEqual(ImageFile(open(self.image_path1)).size, ImageFile(open(self.image_url)).size) self.assertEqual(ImageFile(open(self.image_path1, 'rb')).size, ImageFile(open(self.image_url, 'rb')).size)
self.assertEqual(ImageFile(open(self.image_path2)).size, ImageFile(open(self.image_url2)).size) self.assertEqual(ImageFile(open(self.image_path2, 'rb')).size, ImageFile(open(self.image_url2, 'rb')).size)
def test_get_course_video_image_url(self): def test_get_course_video_image_url(self):
""" """
...@@ -1483,7 +1483,7 @@ class CourseVideoImageTest(TestCase): ...@@ -1483,7 +1483,7 @@ class CourseVideoImageTest(TestCase):
""" """
with self.assertNumQueries(6): with self.assertNumQueries(6):
api.update_video_image( api.update_video_image(
self.edx_video_id, self.course_id, ImageFile(open(self.image_path1)), 'image.jpg' self.edx_video_id, self.course_id, ImageFile(open(self.image_path1, 'rb')), 'image.jpg'
) )
def test_num_queries_get_course_video_image_url(self): def test_num_queries_get_course_video_image_url(self):
...@@ -1517,7 +1517,7 @@ class CourseVideoImageTest(TestCase): ...@@ -1517,7 +1517,7 @@ class CourseVideoImageTest(TestCase):
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, 'rb'))
mock_logger.exception.assert_called_with( mock_logger.exception.assert_called_with(
'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]',
...@@ -1532,11 +1532,11 @@ class CourseVideoImageTest(TestCase): ...@@ -1532,11 +1532,11 @@ class CourseVideoImageTest(TestCase):
does_not_course_id = 'does_not_exist' does_not_course_id = 'does_not_exist'
with self.assertRaises(Exception) as get_exception: 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') api.update_video_image(self.edx_video_id, does_not_course_id, open(self.image_path2, 'rb'), 'test.jpg')
self.assertEqual( self.assertEqual(
get_exception.exception.message, str(get_exception.exception),
u'VAL: CourseVideo not found for edx_video_id: {0} and course_id: {1}'.format( 'VAL: CourseVideo not found for edx_video_id: {0} and course_id: {1}'.format(
self.edx_video_id, self.edx_video_id,
does_not_course_id does_not_course_id
) )
...@@ -1546,7 +1546,7 @@ class CourseVideoImageTest(TestCase): ...@@ -1546,7 +1546,7 @@ class CourseVideoImageTest(TestCase):
""" """
Test `VideoImage.generated_images` field works as expected. Test `VideoImage.generated_images` field works as expected.
""" """
image_urls = ['video-images/a.png', 'video-images/b.png'] image_urls = [str('video-images/a.png'), str('video-images/b.png')]
# an empty list should be returned when there is no value for urls # an empty list should be returned when there is no value for urls
self.assertEqual(self.course_video.video_image.generated_images, []) self.assertEqual(self.course_video.video_image.generated_images, [])
...@@ -1573,7 +1573,7 @@ class CourseVideoImageTest(TestCase): ...@@ -1573,7 +1573,7 @@ class CourseVideoImageTest(TestCase):
self.assertEqual( self.assertEqual(
set_exception.exception.message, set_exception.exception.message,
u'list must not contain more than {} items.'.format(LIST_MAX_ITEMS) 'list must not contain more than {} items.'.format(LIST_MAX_ITEMS)
) )
# expect a validation error if we try to a list with non-string items # expect a validation error if we try to a list with non-string items
...@@ -1581,7 +1581,7 @@ class CourseVideoImageTest(TestCase): ...@@ -1581,7 +1581,7 @@ class CourseVideoImageTest(TestCase):
video_image.generated_images = ['a', 1, 2] video_image.generated_images = ['a', 1, 2]
video_image.save() video_image.save()
self.assertEqual(set_exception.exception.message, u'list must only contain strings.') self.assertEqual(set_exception.exception.message, 'list must only contain strings.')
# expect a validation error if we try to set non list data # expect a validation error if we try to set non list data
for item in ('a string', 555, {'a': 1}, (1,)): for item in ('a string', 555, {'a': 1}, (1,)):
...@@ -1627,7 +1627,7 @@ class CourseVideoImageTest(TestCase): ...@@ -1627,7 +1627,7 @@ class CourseVideoImageTest(TestCase):
# This will replace the image for self.course_video and delete the existing image # This will replace the image for self.course_video and delete the existing image
image_url = api.update_video_image( image_url = api.update_video_image(
self.edx_video_id, self.course_id, ImageFile(open(self.image_path2)), 'image.jpg' self.edx_video_id, self.course_id, ImageFile(open(self.image_path2, 'rb')), 'image.jpg'
) )
# Verify that new image is set to course_video # Verify that new image is set to course_video
...@@ -1636,9 +1636,9 @@ class CourseVideoImageTest(TestCase): ...@@ -1636,9 +1636,9 @@ class CourseVideoImageTest(TestCase):
# Verify that an exception is raised if we try to open a delete image file # Verify that an exception is raised if we try to open a delete image file
with self.assertRaises(IOError) as file_open_exception: with self.assertRaises(IOError) as file_open_exception:
ImageFile(open(existing_image_name)) ImageFile(open(existing_image_name, 'rb'))
self.assertEqual(file_open_exception.exception.strerror, u'No such file or directory') self.assertEqual(file_open_exception.exception.strerror, 'No such file or directory')
def test_video_image_deletion_multiple(self): def test_video_image_deletion_multiple(self):
""" """
...@@ -1651,7 +1651,7 @@ class CourseVideoImageTest(TestCase): ...@@ -1651,7 +1651,7 @@ class CourseVideoImageTest(TestCase):
# This will replace the image for self.course_video but image will # This will replace the image for self.course_video but image will
# not be deleted because it is also used by self.course_video2 # not be deleted because it is also used by self.course_video2
api.update_video_image(self.edx_video_id, self.course_id, ImageFile(open(self.image_path2)), 'image.jpg') api.update_video_image(self.edx_video_id, self.course_id, ImageFile(open(self.image_path2, 'rb')), 'image.jpg')
# Verify image for course_video has changed # Verify image for course_video has changed
course_video = CourseVideo.objects.get(video=self.video, course_id=self.course_id) course_video = CourseVideo.objects.get(video=self.video, course_id=self.course_id)
...@@ -1661,7 +1661,7 @@ class CourseVideoImageTest(TestCase): ...@@ -1661,7 +1661,7 @@ class CourseVideoImageTest(TestCase):
self.assertEqual(self.course_video2.video_image.image.name, shared_image) self.assertEqual(self.course_video2.video_image.image.name, shared_image)
# Open the shared image file to verify it is not deleted # Open the shared image file to verify it is not deleted
ImageFile(open(shared_image)) ImageFile(open(shared_image, 'rb'))
@ddt @ddt
...@@ -1771,7 +1771,7 @@ class TranscriptTest(TestCase): ...@@ -1771,7 +1771,7 @@ class TranscriptTest(TestCase):
""" """
expected_transcript = { expected_transcript = {
'file_name': self.transcript_url, 'file_name': self.transcript_url,
'content': File(open(self.arrow_transcript_path)).read() 'content': File(open(self.arrow_transcript_path, 'rb')).read()
} }
transcript = api.get_video_transcript_data( transcript = api.get_video_transcript_data(
video_ids=['super-soaker', '0987654321'], video_ids=['super-soaker', '0987654321'],
...@@ -1902,7 +1902,7 @@ class TranscriptTest(TestCase): ...@@ -1902,7 +1902,7 @@ class TranscriptTest(TestCase):
with self.assertRaises(exception) as transcript_exception: with self.assertRaises(exception) as transcript_exception:
api.create_or_update_video_transcript(self.video_id, 'ur', 'overwatch.srt', file_format, provider) api.create_or_update_video_transcript(self.video_id, 'ur', 'overwatch.srt', file_format, provider)
self.assertEqual(transcript_exception.exception.message, exception_message) self.assertEqual(str(transcript_exception.exception), exception_message)
def test_video_transcript_deletion(self): def test_video_transcript_deletion(self):
""" """
...@@ -1948,7 +1948,7 @@ class TranscriptTest(TestCase): ...@@ -1948,7 +1948,7 @@ class TranscriptTest(TestCase):
# `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', self.video_id, dupe_lang_video_id, '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']) self.assertEqual(sorted(transcript_languages), sorted(['de', 'en', 'ur']))
@ddt @ddt
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
Tests the serializers for the Video Abstraction Layer Tests the serializers for the Video Abstraction Layer
""" """
from __future__ import unicode_literals
from django.test import TestCase from django.test import TestCase
from edxval.serializers import EncodedVideoSerializer, VideoSerializer from edxval.serializers import EncodedVideoSerializer, VideoSerializer
...@@ -152,7 +153,9 @@ class SerializerTests(TestCase): ...@@ -152,7 +153,9 @@ class SerializerTests(TestCase):
data = "hello" data = "hello"
serializer = VideoSerializer(data=data) serializer = VideoSerializer(data=data)
self.assertFalse(serializer.is_valid()) self.assertFalse(serializer.is_valid())
data_type = type(data).__name__
self.assertEqual( self.assertEqual(
serializer.errors.get("non_field_errors")[0], serializer.errors.get("non_field_errors")[0],
"Invalid data. Expected a dictionary, but got str." "Invalid data. Expected a dictionary, but got {}.".format(data_type)
) )
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
""" """
Tests for Video Abstraction Layer views Tests for Video Abstraction Layer views
""" """
from __future__ import unicode_literals
import json import json
import unittest import unittest
...@@ -446,7 +447,7 @@ class VideoListTest(APIAuthTestCase): ...@@ -446,7 +447,7 @@ class VideoListTest(APIAuthTestCase):
self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.status_code, status.HTTP_201_CREATED)
response = json.loads(response.content) response = json.loads(response.content)
# Check that invalid course keys have been filtered out. # Check that invalid course keys have been filtered out.
self.assertEqual(response['courses'], [{u'edX/DemoX/Astonomy': None}]) self.assertEqual(response['courses'], [{'edX/DemoX/Astonomy': None}])
def test_post_non_latin_client_video_id(self): def test_post_non_latin_client_video_id(self):
""" """
...@@ -475,7 +476,7 @@ class VideoListTest(APIAuthTestCase): ...@@ -475,7 +476,7 @@ class VideoListTest(APIAuthTestCase):
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual( self.assertEqual(
response.data.get("edx_video_id")[0], response.data.get("edx_video_id")[0],
u'video with this edx video id already exists.' 'video with this edx video id already exists.'
) )
videos = len(self.client.get("/edxval/videos/").data) videos = len(self.client.get("/edxval/videos/").data)
self.assertEqual(videos, 1) self.assertEqual(videos, 1)
...@@ -764,19 +765,19 @@ class VideoImagesViewTest(APIAuthTestCase): ...@@ -764,19 +765,19 @@ class VideoImagesViewTest(APIAuthTestCase):
@data( @data(
{ {
'post_data': {}, 'post_data': {},
'message': u'course_id and edx_video_id and generated_images must be specified to update a video image.' 'message': 'course_id and edx_video_id and generated_images must be specified to update a video image.'
}, },
{ {
'post_data': {'course_id': 'does_not_exit_course', 'edx_video_id': 'super-soaker', 'generated_images': []}, 'post_data': {'course_id': 'does_not_exit_course', 'edx_video_id': 'super-soaker', 'generated_images': []},
'message': u'CourseVideo not found for course_id: does_not_exit_course' 'message': 'CourseVideo not found for course_id: does_not_exit_course'
}, },
{ {
'post_data': {'course_id': 'test_course_id', 'edx_video_id': 'does_not_exit_video', 'generated_images': []}, 'post_data': {'course_id': 'test_course_id', 'edx_video_id': 'does_not_exit_video', 'generated_images': []},
'message': u'CourseVideo not found for course_id: test_course_id' 'message': 'CourseVideo not found for course_id: test_course_id'
}, },
{ {
'post_data': {'course_id': 'test_course_id', 'edx_video_id': 'super-soaker', 'generated_images': [1, 2, 3]}, 'post_data': {'course_id': 'test_course_id', 'edx_video_id': 'super-soaker', 'generated_images': [1, 2, 3]},
'message': "[u'list must only contain strings.']" 'message': "'list must only contain strings.'"
}, },
) )
@unpack @unpack
...@@ -788,9 +789,9 @@ class VideoImagesViewTest(APIAuthTestCase): ...@@ -788,9 +789,9 @@ class VideoImagesViewTest(APIAuthTestCase):
response = self.client.post(url, post_data, format='json') response = self.client.post(url, post_data, format='json')
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual( self.assertIn(
response.data['message'], message,
message response.data['message']
) )
......
""" """
Url file for django app edxval. Url file for django app edxval.
""" """
from __future__ import unicode_literals
from django.conf.urls import url from django.conf.urls import url
......
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
Util methods to be used in api and models. Util methods to be used in api and models.
""" """
from __future__ import unicode_literals
from django.conf import settings from django.conf import settings
from django.core.files.storage import get_storage_class from django.core.files.storage import get_storage_class
...@@ -128,7 +130,7 @@ def video_image_path(video_image_instance, filename): # pylint:disable=unused-a ...@@ -128,7 +130,7 @@ def video_image_path(video_image_instance, filename): # pylint:disable=unused-a
video_image_instance (VideoImage): This is passed automatically by models.CustomizableImageField video_image_instance (VideoImage): This is passed automatically by models.CustomizableImageField
filename (str): name of image file filename (str): name of image file
""" """
return u'{}{}'.format(settings.VIDEO_IMAGE_SETTINGS.get('DIRECTORY_PREFIX', ''), filename) return '{}{}'.format(settings.VIDEO_IMAGE_SETTINGS.get('DIRECTORY_PREFIX', ''), filename)
def get_video_image_storage(): def get_video_image_storage():
......
""" """
Views file for django app edxval. Views file for django app edxval.
""" """
from __future__ import unicode_literals
import logging import logging
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
...@@ -229,7 +231,7 @@ class VideoImagesView(APIView): ...@@ -229,7 +231,7 @@ class VideoImagesView(APIView):
return Response( return Response(
status=status.HTTP_400_BAD_REQUEST, status=status.HTTP_400_BAD_REQUEST,
data={ data={
'message': u'{missing} must be specified to update a video image.'.format( 'message': '{missing} must be specified to update a video image.'.format(
missing=' and '.join(missing) missing=' and '.join(missing)
) )
} }
...@@ -241,12 +243,13 @@ class VideoImagesView(APIView): ...@@ -241,12 +243,13 @@ class VideoImagesView(APIView):
try: try:
course_video = CourseVideo.objects.select_related('video_image').get( course_video = CourseVideo.objects.select_related('video_image').get(
course_id=unicode(course_id), video__edx_video_id=edx_video_id course_id=course_id,
video__edx_video_id=edx_video_id
) )
except CourseVideo.DoesNotExist: except CourseVideo.DoesNotExist:
return Response( return Response(
status=status.HTTP_400_BAD_REQUEST, status=status.HTTP_400_BAD_REQUEST,
data={'message': u'CourseVideo not found for course_id: {course_id}'.format(course_id=course_id)} data={'message': 'CourseVideo not found for course_id: {course_id}'.format(course_id=course_id)}
) )
try: try:
......
-e git+https://github.com/edx/django-oauth2-provider.git@0.2.7-fork-edx-6a#egg=django-oauth2-provider==0.2.7-fork-edx-6 -e git+https://github.com/edx/django-oauth2-provider.git@1.2.2#egg=django-oauth2-provider==1.2.2
-e git+https://github.com/edx/django-rest-framework-oauth.git@f0b503fda8c254a38f97fef802ded4f5fe367f7a#egg=djangorestframework-oauth -e git+https://github.com/edx/django-rest-framework-oauth.git@392d3b423788718c04e7aab8dba17a56f95d757f#egg=djangorestframework-oauth
pillow==4.2.1 pillow==4.2.1
boto==2.46.1 boto==2.46.1
django-model-utils==2.3.1 django-model-utils==2.3.1
......
from django.conf import settings
from django.conf.urls import include, url from django.conf.urls import include, url
from django.contrib import admin from django.contrib import admin
......
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