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