Commit 112da93f by christopher lee

POST EncodedVideo set

EncodedVideo part of the upload. Complete EncodedVideo dicts may
now be uploaded. Rejects all EncodedVideos of a Video when there is
an invalid EncodedVideo. Does not reject other Video when a differet
Video is invalid. Currently, EncodedVideo cannot be updated, they can
only be created.

Other notes:
-Added django-debug-toolbar==1.2.1
-constants.py has been reorganized
parent 7a910438
...@@ -108,4 +108,4 @@ def get_video_info(edx_video_id, location=None): ...@@ -108,4 +108,4 @@ def get_video_info(edx_video_id, location=None):
error_message = u"Could not get edx_video_id: {0}".format(edx_video_id) error_message = u"Could not get edx_video_id: {0}".format(edx_video_id)
logger.exception(error_message) logger.exception(error_message)
raise ValInternalError(error_message) raise ValInternalError(error_message)
return result.data return result.data
\ No newline at end of file
...@@ -10,7 +10,10 @@ class Profile(models.Model): ...@@ -10,7 +10,10 @@ class Profile(models.Model):
""" """
Details for pre-defined encoding format Details for pre-defined encoding format
""" """
profile_name = models.CharField(max_length=50, unique=True) profile_name = models.CharField(
max_length=50,
unique=True,
)
extension = models.CharField(max_length=10) extension = models.CharField(max_length=10)
width = models.PositiveIntegerField() width = models.PositiveIntegerField()
height = models.PositiveIntegerField() height = models.PositiveIntegerField()
......
...@@ -2,29 +2,60 @@ ...@@ -2,29 +2,60 @@
Serializers for Video Abstraction Layer Serializers for Video Abstraction Layer
""" """
from rest_framework import serializers from rest_framework import serializers
from rest_framework.serializers import ValidationError
from edxval.models import Profile, Video, EncodedVideo from edxval.models import Profile, Video, EncodedVideo
class VideoSerializer(serializers.ModelSerializer): class DisplayProfileName(serializers.WritableField):
"""
Takes a Profile Object and returns only it's profile_name
"""
def from_native(self, data):
try:
if isinstance(data, int):
profile = Profile.objects.get(pk=data)
elif isinstance(data, unicode):
profile = Profile.objects.get(profile_name=str(data))
return profile
except Profile.DoesNotExist:
error_message = "Profile does not exist: {0}".format(data)
raise ValidationError(error_message)
def restore_object(self, attrs, instance=None): def to_native(self, data):
""" return data.profile_name
Given a dictionary of deserialized field values, either update
an existing model instance, or create a new model instance.
""" class DisplayVideoName(serializers.WritableField):
if instance is not None: """
instance.edx_video_id = attrs.get( Takes a Video Object and returns only it's profile_name
'edx_video_id', instance.edx_video_id """
) def from_native(self, data):
instance.duration = attrs.get( #TODO change doc/function name or modify this function, currently
'duration', instance.duration #TODO takes a video pk and converts it to video Object.
) try:
instance.client_video_id = attrs.get( if isinstance(data, int):
'client_video_id', instance.client_video_id video = Video.objects.get(pk=data)
) return video
return instance except Video.DoesNotExist:
return Video(**attrs) error_message = "Video does not exist: {0}".format(data)
raise ValidationError(error_message)
def to_native(self, data):
return data.edx_video_id
class ListField(serializers.WritableField):
"""
Allows the use of a list as a serializer field.
"""
def from_native(self, data):
if isinstance(data, list):
return data
else:
error_message = "Expecting a list: {0}".format(type(data))
raise ValidationError(error_message)
class Meta: class Meta:
model = Video model = Video
...@@ -45,17 +76,59 @@ class ProfileSerializer(serializers.ModelSerializer): ...@@ -45,17 +76,59 @@ class ProfileSerializer(serializers.ModelSerializer):
) )
class OnlyEncodedVideoSerializer(serializers.ModelSerializer): class VideoSerializer(serializers.ModelSerializer):
def restore_object(self, attrs, instance=None):
"""
Given a dictionary of deserialized field values, either update
an existing model instance, or create a new model instance.
"""
if instance is not None:
instance.edx_video_id = attrs.get('edx_video_id', instance.edx_video_id)
instance.duration = attrs.get('duration', instance.duration)
instance.client_video_id = attrs.get('client_video_id', instance.client_video_id)
return instance
return Video(**attrs)
class Meta:
model = Video
fields = (
"client_video_id",
"duration",
"edx_video_id"
)
class EncodedVideoSerializer(serializers.ModelSerializer):
""" """
Used to serialize the EncodedVideo for the EncodedVideoSetSerializer Used to serialize the EncodedVideo for the EncodedVideoSetSerializer
""" """
profile = ProfileSerializer(required=False) profile = DisplayProfileName()
video = DisplayVideoName()
def restore_object(self, attrs, instance=None):
"""
Given a dictionary of deserialized field values, either update
an existing model instance, or create a new model instance.
"""
if instance is not None:
#TODO Currently not updating Encodedvideo Object.
instance.url = attrs.get('url', instance.url)
instance.file_size = attrs.get('file_size', instance.file_size)
instance.bitrate = attrs.get('bitrate', instance.bitrate)
#TODO profile instance.profile = attrs.get('profile', instance.profile)
return instance
return EncodedVideo(**attrs)
class Meta: class Meta:
model = EncodedVideo model = EncodedVideo
fields = ( fields = (
"url", "url",
"file_size", "file_size",
"bitrate" "bitrate",
"profile",
"video"
) )
...@@ -64,12 +137,114 @@ class EncodedVideoSetSerializer(serializers.ModelSerializer): ...@@ -64,12 +137,114 @@ class EncodedVideoSetSerializer(serializers.ModelSerializer):
Used to serialize a list of EncodedVideo objects it's foreign key Video Object. Used to serialize a list of EncodedVideo objects it's foreign key Video Object.
""" """
edx_video_id = serializers.CharField(max_length=50) edx_video_id = serializers.CharField(max_length=50)
encoded_videos = OnlyEncodedVideoSerializer() encoded_videos = EncodedVideoSerializer(required=False)
class Meta: class Meta:
model = Video model = Video
fields = ( fields = (
"duration", "duration",
"client_video_id" "client_video_id",
"edx_video_id",
"encoded_videos"
) )
class EncodedVideoSetDeserializer(serializers.Serializer):
"""
Deserializes a dict of Video fields and list of EncodedVideos.
Example:
>>>data = dict(
... client_video_id="Shallow Swordfish",
... duration=122.00,
... edx_video_id="supersoaker",
... encoded_videos=[
... dict(
... url="https://www.swordsingers.com",
... file_size=9000,
... bitrate=42,
... profile=1,
... ),
... dict(
... url="https://www.swordsingers.com",
... file_size=9000,
... bitrate=42,
... profile=2,
... )
... ]
... )
>>>serilizer = EncodedVideoSetDeserializer(data=data)
>>>if serilizer.is_valid()
>>> serializer.save()
>>>video = Video.objects.get(edx_video_id="supersoaker")
>>>print EncodedVideoSetDeserializer(video).data
{
'duration': 122.0,
'client_video_id': u'ShallowSwordfish',
'edx_video_id': u'supersoaker',
'encoded_videos': [
{
'url': u'https: //www.swordsingers.com',
'file_size': 9000,
'bitrate': 42,
'profile': u'mobile',
'video': u'supersoaker'
},
{
'url': u'https: //www.swordsingers.com',
'file_size': 9000,
'bitrate': 42,
'profile': u'desktop',
'video': u'supersoaker'
}
]
}
"""
client_video_id = serializers.CharField(max_length=50)
duration = serializers.FloatField()
edx_video_id = serializers.CharField(max_length=50)
encoded_videos = ListField(required=False)
def restore_object(self, attrs, instance=None):
"""
Updates or creates video object and creates valid EncodedVideo objects.
If the Video parameters are not valid, the errors are returns and the
Video object is not created and the desrialization ends. If any of the
EncodedVideo Parameters are invalid, the errors are returns and the
EncodedVideos objects are not created.
"""
#Get or create the Video object, else return errors
try:
instance = Video.objects.get(edx_video_id=attrs.get("edx_video_id"))
except Video.DoesNotExist:
instance = None
video = VideoSerializer(
data=dict(
edx_video_id=attrs.get("edx_video_id"),
duration=attrs.get("duration"),
client_video_id=attrs.get("client_video_id")
),
instance=instance
)
if video.is_valid():
video.save()
else:
for key in video.errors:
self.errors[key] = video.errors[key]
return
#Point encoded_videos to parent video
if not "encoded_videos" in attrs:
return video
for item in attrs.get("encoded_videos"):
item[u"video"] = Video.objects.get(edx_video_id=attrs.get("edx_video_id")).pk
#Serialize EncodedVideos, else raise errors
ev = EncodedVideoSerializer(data=attrs.get("encoded_videos"), many=True)
if ev.is_valid():
ev.save()
else:
self.errors["encoded_videos"] = ev.errors
return video
\ No newline at end of file
...@@ -124,6 +124,7 @@ INSTALLED_APPS = ( ...@@ -124,6 +124,7 @@ INSTALLED_APPS = (
'rest_framework', 'rest_framework',
# Uncomment the next line to enable the admin: # Uncomment the next line to enable the admin:
'django.contrib.admin', 'django.contrib.admin',
'debug_toolbar'
# Uncomment the next line to enable admin documentation: # Uncomment the next line to enable admin documentation:
# 'django.contrib.admindocs', # 'django.contrib.admindocs',
) )
...@@ -165,4 +166,4 @@ LOGGING = { ...@@ -165,4 +166,4 @@ LOGGING = {
'propagate': True, 'propagate': True,
}, },
} }
} }
\ No newline at end of file
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
EDX_VIDEO_ID = "thisis12char-thisis7" EDX_VIDEO_ID = "itchyjacket"
"""
Generic Profiles for manually creating profile objects
"""
PROFILE_DICT_MOBILE = dict(
profile_name="mobile",
extension="avi",
width=100,
height=101
)
PROFILE_DICT_DESKTOP = dict(
profile_name="desktop",
extension="mp4",
width=200,
height=2001
)
"""
Encoded_videos for test_api, does not have profile.
"""
ENCODED_VIDEO_DICT_MOBILE = dict( ENCODED_VIDEO_DICT_MOBILE = dict(
url="http://www.meowmix.com", url="http://www.meowmix.com",
file_size=25556, file_size=4545,
bitrate=9600, bitrate=6767,
) )
ENCODED_VIDEO_DICT_DESKTOP = dict( ENCODED_VIDEO_DICT_DESKTOP = dict(
url="http://www.meowmagic.com", url="http://www.meowmagic.com",
file_size=25556, file_size=1212,
bitrate=9600, bitrate=2323,
)
"""
Validators
"""
VIDEO_DICT_NEGATIVE_DURATION = dict(
client_video_id="Thunder Cats S01E01",
duration=-111,
edx_video_id="thisis12char-thisis7",
) )
ENCODED_VIDEO_DICT_NEGATIVE_FILESIZE = dict( ENCODED_VIDEO_DICT_NEGATIVE_FILESIZE = dict(
url="http://www.meowmix.com", url="http://www.meowmix.com",
file_size=-25556, file_size=-25556,
...@@ -23,19 +47,18 @@ ENCODED_VIDEO_DICT_NEGATIVE_BITRATE = dict( ...@@ -23,19 +47,18 @@ ENCODED_VIDEO_DICT_NEGATIVE_BITRATE = dict(
file_size=25556, file_size=25556,
bitrate=-9600, bitrate=-9600,
) )
"""
PROFILE_DICT_MOBILE = dict( Non-latin
profile_name="mobile", """
extension="avi", VIDEO_DICT_NON_LATIN_TITLE = dict(
width=100, client_video_id="배고픈 햄스터",
height=101 duration=42,
edx_video_id="ID"
) )
VIDEO_DICT_NON_LATIN_ID = dict(
PROFILE_DICT_DESKTOP = dict( client_video_id="Hungry Hamster",
profile_name="desktop", duration=42,
extension="mp4", edx_video_id="밥줘"
width=200,
height=2001
) )
PROFILE_DICT_NON_LATIN = dict( PROFILE_DICT_NON_LATIN = dict(
profile_name=u"배고파", profile_name=u"배고파",
...@@ -43,71 +66,148 @@ PROFILE_DICT_NON_LATIN = dict( ...@@ -43,71 +66,148 @@ PROFILE_DICT_NON_LATIN = dict(
width=100, width=100,
height=300 height=300
) )
VIDEO_DICT_CATS = dict( """
client_video_id="Thunder Cats S01E01", Fish
duration=111.00, """
edx_video_id="thisis12char-thisis7", VIDEO_DICT_FISH = dict(
client_video_id="Shallow Swordfish",
duration=122.00,
edx_video_id="supersoaker"
) )
VIDEO_DICT_LION = dict( ENCODED_VIDEO_DICT_FISH_MOBILE = dict(
client_video_id="Lolcat", url="https://www.swordsingers.com",
duration=111.00, file_size=9000,
edx_video_id="caw", bitrate=42,
profile="mobile",
) )
VIDEO_DICT_LION2 = dict(
client_video_id="Lolcat", ENCODED_VIDEO_DICT_FISH_DESKTOP = dict(
url="https://www.swordsplints.com",
file_size=1234,
bitrate=4222,
profile="desktop",
)
ENCODED_VIDEO_DICT_FISH_INVALID_PROFILE = dict(
url="https://www.swordsplints.com",
file_size=1234,
bitrate=4222,
profile=11,
)
COMPLETE_SET_FISH = dict(
encoded_videos=[
ENCODED_VIDEO_DICT_FISH_MOBILE,
ENCODED_VIDEO_DICT_FISH_DESKTOP
],
**VIDEO_DICT_FISH
)
COMPLETE_SET_INVALID_ENCODED_VIDEO_FISH = dict(
encoded_videos=[
ENCODED_VIDEO_DICT_FISH_MOBILE,
ENCODED_VIDEO_DICT_FISH_INVALID_PROFILE
],
**VIDEO_DICT_FISH
)
COMPLETE_SET_INVALID_VIDEO_FISH = dict(
client_video_id="Shallow Swordfish",
duration=122.00, duration=122.00,
edx_video_id="caw", edx_video_id="super/soaker",
encoded_videos=[
ENCODED_VIDEO_DICT_FISH_MOBILE,
ENCODED_VIDEO_DICT_FISH_DESKTOP
]
) )
VIDEO_DICT_TIGERS_BEARS = [ COMPLETE_SETS_ALL_INVALID = [
dict( COMPLETE_SET_INVALID_VIDEO_FISH,
client_video_id="Tipsy Tiger", COMPLETE_SET_INVALID_VIDEO_FISH
duration=111.00,
edx_video_id="meeeeeow",
),
dict(
client_video_id="Boring Bear",
duration=111.00,
edx_video_id="hithar",
)
] ]
VIDEO_DICT_INVALID_SET = [
dict(
client_video_id="Average Animal",
duration=111.00,
edx_video_id="mediocrity",
),
dict(
client_video_id="Barking Bee",
duration=111.00,
edx_video_id="wa/sps",
),
dict(
client_video_id="Callous Coat",
duration=111.00,
edx_video_id="not an animal",
)
]
VIDEO_DICT_DUPLICATES = [ """
dict( Star
client_video_id="Gaggling gopher", """
duration=111.00, VIDEO_DICT_STAR = dict(
edx_video_id="gg", client_video_id="TWINKLE TWINKLE",
), duration=122.00,
dict( edx_video_id="little-star"
client_video_id="Gaggling gopher", )
duration=111.00, ENCODED_VIDEO_DICT_STAR = dict(
edx_video_id="gg", url="https://www.howIwonder.com",
file_size=9000,
bitrate=42,
profile=1
)
ENCODED_VIDEO_DICT_STAR2 = dict(
url="https://www.whatyouare.com",
file_size=1111,
bitrate=2333,
profile=2
)
COMPLETE_SET_STAR = dict(
encoded_videos=[
ENCODED_VIDEO_DICT_STAR,
ENCODED_VIDEO_DICT_STAR2
],
**VIDEO_DICT_STAR
)
COMPLETE_SET_NOT_A_LIST = dict(
encoded_videos=dict(
url="https://www.howIwonder.com",
file_size=9000,
bitrate=42,
profile=1
), ),
] **VIDEO_DICT_STAR
)
VIDEO_DICT_NEGATIVE_DURATION = dict( COMPLETE_SET_EXTRA_VIDEO_FIELD = dict(
client_video_id="Thunder Cats S01E01", encoded_videos=[
duration=-111, dict(
edx_video_id="thisis12char-thisis7", url="https://www.vulturevideos.com",
file_size=101010,
bitrate=1234,
profile=1,
video="This should be overridden by parent videofield"
)
],
**VIDEO_DICT_STAR
)
"""
Unsorted
"""
VIDEO_DICT_COAT = dict(
client_video_id="Callous Coat",
duration=111.00,
edx_video_id="itchyjacket",
)
VIDEO_DICT_AVERAGE = dict(
client_video_id="Average Animal",
duration=111.00,
edx_video_id="mediocrity",
)
VIDEO_DICT_AVERAGE2 = dict(
client_video_id="Lolcat",
duration=122.00,
edx_video_id="mediocrity",
)
VIDEO_DICT_CRAYFISH = dict(
client_video_id="Crazy Crayfish",
duration=111.00,
edx_video_id="craycray",
)
VIDEO_DICT_BEE_INVALID = dict(
client_video_id="Barking Bee",
duration=111.00,
edx_video_id="wa/sps",
) )
VIDEO_DICT_INVALID_ID = dict( VIDEO_DICT_INVALID_ID = dict(
...@@ -116,14 +216,30 @@ VIDEO_DICT_INVALID_ID = dict( ...@@ -116,14 +216,30 @@ VIDEO_DICT_INVALID_ID = dict(
edx_video_id="sloppy/sloth!!" edx_video_id="sloppy/sloth!!"
) )
VIDEO_DICT_NON_LATIN_TITLE = dict( VIDEO_DICT_DUPLICATES = [
client_video_id="배고픈 햄스터", VIDEO_DICT_CRAYFISH,
duration=42, VIDEO_DICT_CRAYFISH,
edx_video_id="ID" VIDEO_DICT_CRAYFISH
) ]
VIDEO_DICT_NON_LATIN_ID = dict( COMPLETE_SETS = [
client_video_id="Hungry Hamster", COMPLETE_SET_STAR,
duration=42, COMPLETE_SET_FISH
edx_video_id="밥줘" ]
)
\ No newline at end of file COMPLETE_SETS_ONE_INVALID = [
COMPLETE_SET_STAR,
COMPLETE_SET_INVALID_VIDEO_FISH
]
VIDEO_DICT_SET_OF_THREE = [
VIDEO_DICT_COAT,
VIDEO_DICT_AVERAGE,
VIDEO_DICT_CRAYFISH
]
VIDEO_DICT_INVALID_SET = [
VIDEO_DICT_COAT,
VIDEO_DICT_INVALID_ID,
VIDEO_DICT_BEE_INVALID
]
...@@ -25,17 +25,17 @@ class GetVideoInfoTest(TestCase): ...@@ -25,17 +25,17 @@ class GetVideoInfoTest(TestCase):
""" """
Profile.objects.create(**constants.PROFILE_DICT_MOBILE) Profile.objects.create(**constants.PROFILE_DICT_MOBILE)
Profile.objects.create(**constants.PROFILE_DICT_DESKTOP) Profile.objects.create(**constants.PROFILE_DICT_DESKTOP)
Video.objects.create(**constants.VIDEO_DICT_CATS) Video.objects.create(**constants.VIDEO_DICT_COAT)
EncodedVideo.objects.create( EncodedVideo.objects.create(
video=Video.objects.get( video=Video.objects.get(
edx_video_id=constants.VIDEO_DICT_CATS.get("edx_video_id") edx_video_id=constants.VIDEO_DICT_COAT.get("edx_video_id")
), ),
profile=Profile.objects.get(profile_name="mobile"), profile=Profile.objects.get(profile_name="mobile"),
**constants.ENCODED_VIDEO_DICT_MOBILE **constants.ENCODED_VIDEO_DICT_MOBILE
) )
EncodedVideo.objects.create( EncodedVideo.objects.create(
video=Video.objects.get( video=Video.objects.get(
edx_video_id=constants.VIDEO_DICT_CATS.get("edx_video_id") edx_video_id=constants.VIDEO_DICT_COAT.get("edx_video_id")
), ),
profile=Profile.objects.get(profile_name="desktop"), profile=Profile.objects.get(profile_name="desktop"),
**constants.ENCODED_VIDEO_DICT_DESKTOP **constants.ENCODED_VIDEO_DICT_DESKTOP
...@@ -77,4 +77,4 @@ class GetVideoInfoTest(TestCase): ...@@ -77,4 +77,4 @@ class GetVideoInfoTest(TestCase):
""" """
mock_get.side_effect = DatabaseError("DatabaseError") mock_get.side_effect = DatabaseError("DatabaseError")
with self.assertRaises(api.ValInternalError): with self.assertRaises(api.ValInternalError):
api.get_video_info(constants.EDX_VIDEO_ID) api.get_video_info(constants.EDX_VIDEO_ID)
\ No newline at end of file
...@@ -6,12 +6,14 @@ Tests the serializers for the Video Abstraction Layer ...@@ -6,12 +6,14 @@ Tests the serializers for the Video Abstraction Layer
from django.test import TestCase from django.test import TestCase
from edxval.serializers import ( from edxval.serializers import (
OnlyEncodedVideoSerializer, EncodedVideoSerializer,
EncodedVideoSetSerializer, EncodedVideoSetSerializer,
ProfileSerializer, ProfileSerializer,
VideoSerializer VideoSerializer,
DisplayProfileName
) )
from edxval.models import Profile from edxval.models import Profile, Video, EncodedVideo
from edxval.tests import constants from edxval.tests import constants
...@@ -24,6 +26,7 @@ class SerializerTests(TestCase): ...@@ -24,6 +26,7 @@ class SerializerTests(TestCase):
Creates EncodedVideo objects in database Creates EncodedVideo objects in database
""" """
Profile.objects.create(**constants.PROFILE_DICT_MOBILE) Profile.objects.create(**constants.PROFILE_DICT_MOBILE)
Profile.objects.create(**constants.PROFILE_DICT_DESKTOP)
Profile.objects.create(**constants.PROFILE_DICT_NON_LATIN) Profile.objects.create(**constants.PROFILE_DICT_NON_LATIN)
def test_negative_fields_only_encoded_video(self): def test_negative_fields_only_encoded_video(self):
...@@ -32,11 +35,12 @@ class SerializerTests(TestCase): ...@@ -32,11 +35,12 @@ class SerializerTests(TestCase):
Tests negative inputs for bitrate, file_size in EncodedVideo Tests negative inputs for bitrate, file_size in EncodedVideo
""" """
a = OnlyEncodedVideoSerializer( a = EncodedVideoSerializer(
data=constants.ENCODED_VIDEO_DICT_NEGATIVE_BITRATE).errors data=constants.ENCODED_VIDEO_DICT_NEGATIVE_BITRATE).errors
self.assertEqual(a.get('bitrate')[0], self.assertEqual(a.get('bitrate')[0],
u"Ensure this value is greater than or equal to 0.") u"Ensure this value is greater than or equal to 0.")
b = OnlyEncodedVideoSerializer( b = EncodedVideoSerializer(
data=constants.ENCODED_VIDEO_DICT_NEGATIVE_FILESIZE).errors data=constants.ENCODED_VIDEO_DICT_NEGATIVE_FILESIZE).errors
self.assertEqual(b.get('file_size')[0], self.assertEqual(b.get('file_size')[0],
u"Ensure this value is greater than or equal to 0.") u"Ensure this value is greater than or equal to 0.")
...@@ -56,6 +60,7 @@ class SerializerTests(TestCase): ...@@ -56,6 +60,7 @@ class SerializerTests(TestCase):
""" """
Tests if the serializers can accept non-latin chars Tests if the serializers can accept non-latin chars
""" """
#TODO not the best test. Need to understand what result we want
self.assertIsNotNone( self.assertIsNotNone(
ProfileSerializer(Profile.objects.get(profile_name="배고파")) ProfileSerializer(Profile.objects.get(profile_name="배고파"))
) )
...@@ -68,4 +73,22 @@ class SerializerTests(TestCase): ...@@ -68,4 +73,22 @@ class SerializerTests(TestCase):
message = error.get("edx_video_id")[0] message = error.get("edx_video_id")[0]
self.assertEqual( self.assertEqual(
message, message,
u"edx_video_id has invalid characters") u"edx_video_id has invalid characters")
\ No newline at end of file
def test_encoded_video_set_output(self):
"""
Tests for basic structure of EncodedVideoSetSerializer
"""
video = Video.objects.create(**constants.VIDEO_DICT_COAT)
EncodedVideo.objects.create(
video=video,
profile=Profile.objects.get(profile_name="desktop"),
**constants.ENCODED_VIDEO_DICT_DESKTOP
)
EncodedVideo.objects.create(
video=video,
profile=Profile.objects.get(profile_name="mobile"),
**constants.ENCODED_VIDEO_DICT_MOBILE
)
result = EncodedVideoSetSerializer(video).data
self.assertEqual(len(result.get("encoded_videos")), 2)
...@@ -3,76 +3,129 @@ from rest_framework import status ...@@ -3,76 +3,129 @@ from rest_framework import status
from rest_framework.test import APITestCase from rest_framework.test import APITestCase
from edxval.tests import constants from edxval.tests import constants
from edxval.models import Profile, Video
from edxval.serializers import EncodedVideoSetSerializer
class VideoListTest(APITestCase): class VideoListTest(APITestCase):
""" """
Tests the creations of Videos via POST/GET Tests the creations of Videos via POST/GET
""" """
def setUp(self):
"""
SetUp
"""
Profile.objects.create(**constants.PROFILE_DICT_MOBILE)
Profile.objects.create(**constants.PROFILE_DICT_DESKTOP)
def test_post_video(self): """
Tests for successful POST requests.
These tests should be returning HTTP_201_CREATED responses.
"""
def test_complete_set_two_encoded_video_post(self):
""" """
Tests creating a new Video object via POST Tests POSTing Video and EncodedVideo pair
""" """
url = reverse('video_view') url = reverse('video_view')
response = self.client.post( response = self.client.post(
url, [constants.VIDEO_DICT_LION], format='json' url, constants.COMPLETE_SET_FISH, format='json'
) )
self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.status_code, status.HTTP_201_CREATED)
video = self.client.get("/edxval/video/").data
self.assertEqual(len(video), 1)
result = EncodedVideoSetSerializer(Video.objects.all()[0]).data
self.assertEqual(len(result.get("encoded_videos")), 2)
self.assertEqual(response.data, "Success")
def test_get_all_videos(self): # def test_update_already_existing_complete_set(self):
""" # """
Tests getting all Video objects # Tests the update of an already existing Video and its EncodedVideos via POST
""" # """
url = reverse('video_view') # #TODO This test will fail until video/profile are unique_together
self.client.post(url, [constants.VIDEO_DICT_LION], format='json') # #TODO and when profiles are updated instead of created
self.client.post(url, [constants.VIDEO_DICT_CATS], format='json') # url = reverse('video_view')
videos = len(self.client.get("/edxval/video/").data) # response = self.client.post(
self.assertEqual(videos, 2) # url, constants.COMPLETE_SET_FISH, format='json'
# )
# self.assertEqual(response.status_code, status.HTTP_201_CREATED)
# response = self.client.post(
# url, constants.COMPLETE_SET_STAR, format='json'
# )
# video = self.client.get("/edxval/video/").data
# self.assertEqual(len(video), 1)
# result = EncodedVideoSetSerializer(Video.objects.all()[0]).data
# self.assertEqual(len(result.get("encoded_videos")), 2)
# self.assertEqual(response.data, "Success")
def test_post_multiple_valid_video_creation(self): def test_complete_set_with_extra_video_field(self):
""" """
Tests the creation of more than one video Tests the case where there is an additional unneeded video field vis POST
""" """
url = reverse('video_view') url = reverse('video_view')
response = self.client.post( response = self.client.post(
url, constants.VIDEO_DICT_TIGERS_BEARS, format='json' url, constants.COMPLETE_SET_EXTRA_VIDEO_FIELD, format='json'
) )
self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.status_code, status.HTTP_201_CREATED)
videos = len(self.client.get("/edxval/video/").data) video = self.client.get("/edxval/video/").data
self.assertEqual(videos, 2) self.assertEqual(len(video), 1)
edx_video_id = constants.VIDEO_DICT_STAR.get("edx_video_id")
self.assertEqual(video[0].get("edx_video_id"), edx_video_id)
def test_post_invalid_video_entry(self): def test_post_video(self):
""" """
Tests for invalid video entry for POST Tests POSTing a new Video object
""" """
url = reverse('video_view') url = reverse('video_view')
response = self.client.post(url, [constants.VIDEO_DICT_INVALID_ID], format='json') response = self.client.post(
error = len(response.data) url, [constants.VIDEO_DICT_AVERAGE], format='json'
self.assertEqual(error, 1) )
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
result = EncodedVideoSetSerializer(Video.objects.all()[0]).data
self.assertEqual(len(result.get("encoded_videos")), 0)
def test_post_invalid_entry(self): def test_post_videos(self):
""" """
Tests when a non list POST request is made Tests POSTing same video
""" """
url = reverse('video_view') url = reverse('video_view')
response = self.client.post(url, constants.VIDEO_DICT_CATS, format='json') response = self.client.post(
self.assertEqual(response.data, "Not a list: <type 'dict'>") url, [constants.VIDEO_DICT_AVERAGE], format='json'
)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
videos = len(self.client.get("/edxval/video/").data)
self.assertEqual(videos, 1)
response = self.client.post(
url, [constants.VIDEO_DICT_AVERAGE], format='json'
)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
videos = len(self.client.get("/edxval/video/").data)
self.assertEqual(videos, 1)
response = self.client.post(
url, [constants.VIDEO_DICT_AVERAGE], format='json'
)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
videos = len(self.client.get("/edxval/video/").data)
self.assertEqual(videos, 1)
def test_post_invalid_video_dict_list(self): def test_post_multiple_valid_video_creation(self):
""" """
Tests when there are valid and invalid dicts in list Tests POSTing than one valid videos
Input is a list of video dicts
""" """
url = reverse('video_view') url = reverse('video_view')
response = self.client.post(url, constants.VIDEO_DICT_INVALID_SET, format='json') response = self.client.post(
errors = len(response.data) url, constants.VIDEO_DICT_SET_OF_THREE, format='json'
self.assertEqual(errors, 2) )
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
videos = len(self.client.get("/edxval/video/").data) videos = len(self.client.get("/edxval/video/").data)
self.assertEqual(videos, 1) self.assertEqual(videos, 3)
def test_post_valid_video_dict_list_duplicates(self): def test_post_valid_video_dict_list_duplicates(self):
""" """
Tests when valid duplicate dicts are submitted in a list Tests when POSTing valid duplicate dicts in a list
""" """
url = reverse('video_view') url = reverse('video_view')
response = self.client.post(url, constants.VIDEO_DICT_DUPLICATES, format='json') response = self.client.post(url, constants.VIDEO_DICT_DUPLICATES, format='json')
...@@ -80,45 +133,187 @@ class VideoListTest(APITestCase): ...@@ -80,45 +133,187 @@ class VideoListTest(APITestCase):
videos = len(self.client.get("/edxval/video/").data) videos = len(self.client.get("/edxval/video/").data)
self.assertEqual(videos, 1) self.assertEqual(videos, 1)
def test_post_non_latin_dict(self): def test_post_non_latin_client_video_id(self):
""" """
Tests a non-latin character input Tests POSTing non-latin client_video_id
""" """
url = reverse('video_view') url = reverse('video_view')
response = self.client.post(url, [constants.VIDEO_DICT_NON_LATIN_ID], format='json')
errors = len(response.data)
self.assertEqual(errors, 1)
response = self.client.post(url, [constants.VIDEO_DICT_NON_LATIN_TITLE], format='json') response = self.client.post(url, [constants.VIDEO_DICT_NON_LATIN_TITLE], format='json')
self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.status_code, status.HTTP_201_CREATED)
def test_post_update_video(self): def test_post_update_video(self):
""" """
Tests video update Tests POST video update
""" """
url = reverse('video_view') url = reverse('video_view')
self.client.post(url, [constants.VIDEO_DICT_LION], format='json') response = self.client.post(url, [constants.VIDEO_DICT_AVERAGE], format='json')
response = self.client.post(url, [constants.VIDEO_DICT_LION2], format='json')
self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.status_code, status.HTTP_201_CREATED)
videos = len(self.client.get("/edxval/video/").data) response = self.client.post(url, [constants.VIDEO_DICT_AVERAGE2], format='json')
self.assertEqual(videos, 1) self.assertEqual(response.status_code, status.HTTP_201_CREATED)
old_duration = constants.VIDEO_DICT_LION.get("duration") videos = self.client.get("/edxval/video/").data
new_duration = constants.VIDEO_DICT_LION2.get("duration") self.assertEqual(len(videos), 1)
old_duration = constants.VIDEO_DICT_AVERAGE.get("duration")
new_duration = videos[0].get("duration")
self.assertNotEqual(new_duration, old_duration) self.assertNotEqual(new_duration, old_duration)
old_client_video_id = constants.VIDEO_DICT_AVERAGE.get("client_video_id")
new_client_video_id = videos[0].get("client_video_id")
self.assertNotEqual(new_client_video_id, old_client_video_id)
def test_post_an_encoded_video_for_different_video(self):
"""
Tests POSTing encoded videos for different videos
"""
url = reverse('video_view')
response = self.client.post(
url, constants.COMPLETE_SETS, format='json'
)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
videos = self.client.get("/edxval/video/").data
self.assertEqual(len(videos), 2)
self.assertNotEqual(videos[0], videos[1])
class VideoDetailTest(APITestCase):
""" """
Tests for the VideoDetail class Tests for POSTing invalid data
These tests should be returning HTTP_400_BAD_REQUEST
""" """
def test_get_video(self): def test_complete_set_invalid_encoded_video_post(self):
""" """
Tests retrieving a particular Video Object Tests POSTing valid Video and partial valid EncodedVideos.
""" """
url = reverse('video_view') url = reverse('video_view')
self.client.post(url, [constants.VIDEO_DICT_LION], format='json') response = self.client.post(
search = "/edxval/video/{0}".format(constants.VIDEO_DICT_LION.get("edx_video_id")) url, constants.COMPLETE_SET_INVALID_ENCODED_VIDEO_FISH, format='json'
response = self.client.get(search) )
a = response.data.get("edx_video_id") self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
b = constants.VIDEO_DICT_LION.get("edx_video_id") video = self.client.get("/edxval/video/").data
self.assertEqual(a, b) self.assertEqual(len(video), 1)
\ No newline at end of file result = EncodedVideoSetSerializer(Video.objects.all()[0]).data
self.assertEqual(len(result.get("encoded_videos")), 0)
self.assertIsNotNone(response.data.get("encoded_videos")[0])
def test_complete_set_invalid_video_post(self):
"""
Tests invalid Video POST
"""
url = reverse('video_view')
response = self.client.post(
url, constants.COMPLETE_SET_INVALID_VIDEO_FISH, format='json'
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
video = self.client.get("/edxval/video/").data
self.assertEqual(len(video), 0)
self.assertEqual(
response.data.get("edx_video_id")[0],
"edx_video_id has invalid characters"
)
def test_complete_set_not_a_list(self):
"""
Tests POST when the encoded videos are not a list of dicts
"""
url = reverse('video_view')
response = self.client.post(
url, constants.COMPLETE_SET_NOT_A_LIST, format='json'
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(
response.data,
{'encoded_videos':[u"Expecting a list: <type 'dict'>"]}
)
def test_post_invalid_video_entry(self):
"""
Tests for invalid video entry for POST
"""
url = reverse('video_view')
response = self.client.post(url, [constants.VIDEO_DICT_INVALID_ID], format='json')
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
errors = response.data
self.assertEqual(
errors[0].get("edx_video_id")[0],
"edx_video_id has invalid characters"
)
def test_post_invalid_video_dict_list(self):
"""
Tests POSTing a list of invalid Video dicts
Input is a list of video dicts where [{valid},{invalid},{invalid}]
"""
url = reverse('video_view')
response = self.client.post(url, constants.VIDEO_DICT_INVALID_SET, format='json')
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
errors = response.data
self.assertEqual(
errors[1].get("edx_video_id")[0],
"edx_video_id has invalid characters"
)
self.assertEqual(
errors[2].get("edx_video_id")[0],
"edx_video_id has invalid characters"
)
videos = len(self.client.get("/edxval/video/").data)
self.assertEqual(videos, 1)
def test_post_non_latin_edx_video_id(self):
"""
Tests POSTing non-latin edx_video_id
"""
url = reverse('video_view')
response = self.client.post(url, [constants.VIDEO_DICT_NON_LATIN_ID], format='json')
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
def test_post_an_encoded_video_for_different_video(self):
"""
Tests POSTing a list of Videos, [{valid}, {invalid}]
"""
url = reverse('video_view')
response = self.client.post(
url, constants.COMPLETE_SETS_ONE_INVALID, format='json'
)
errors = response.data
self.assertEqual(
errors[1].get("edx_video_id")[0],
"edx_video_id has invalid characters"
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
videos = self.client.get("/edxval/video/").data
self.assertEqual(len(videos), 1)
def test_post_all_invalid_videos(self):
"""
Tests POSTing a list of Videos, [{invalid}, {invalid}]
"""
url = reverse('video_view')
response = self.client.post(
url, constants.COMPLETE_SETS_ALL_INVALID, format='json'
)
errors = response.data
self.assertEqual(
errors[1].get("edx_video_id")[0],
"edx_video_id has invalid characters"
)
self.assertEqual(
errors[0].get("edx_video_id")[0],
"edx_video_id has invalid characters"
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
videos = self.client.get("/edxval/video/").data
self.assertEqual(len(videos), 0)
"""
Tests for GET
"""
def test_get_all_videos(self):
"""
Tests getting all Video objects
"""
url = reverse('video_view')
response = self.client.post(url, [constants.VIDEO_DICT_AVERAGE], format='json')
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
response = self.client.post(url, [constants.VIDEO_DICT_COAT], format='json')
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
videos = len(self.client.get("/edxval/video/").data)
self.assertEqual(videos, 2)
...@@ -3,12 +3,15 @@ from rest_framework.response import Response ...@@ -3,12 +3,15 @@ from rest_framework.response import Response
from rest_framework import status, generics from rest_framework import status, generics
from edxval.models import Video from edxval.models import Video
from edxval.serializers import VideoSerializer from edxval.serializers import VideoSerializer, EncodedVideoSetDeserializer, EncodedVideoSetSerializer
class VideoList(APIView): class VideoList(APIView):
""" """
HTTP API for Video objects <<<<<<< HEAD
HTTP API for Video and EncodedVideo objects
""" """
def get(self, request, format=None): def get(self, request, format=None):
...@@ -16,44 +19,27 @@ class VideoList(APIView): ...@@ -16,44 +19,27 @@ class VideoList(APIView):
Gets all videos Gets all videos
""" """
video = Video.objects.all() video = Video.objects.all()
serializer = VideoSerializer(video, many=True) serializer = EncodedVideoSetSerializer(video, many=True)
return Response(serializer.data) return Response(serializer.data)
def post(self, request, format=None): def post(self, request, format=None):
""" """
Takes an object (where we get our list of dict) and creates the objects Takes a Video dict of a list of EncodedVideo dicts and creates the objects
Request.DATA is a list of dictionaries. Each item is individually validated
and if valid, saved. All invalid dicts are returned in the error message.
Args: Args:
request (object): Object where we get our information for POST request (object): Object where we get our information for POST
format (str): format of our data (JSON, XML, etc.) data_format (str): format of our data (JSON, XML, etc.)
Returns: Returns:
Response(message, HTTP status) Response(message, HTTP status)
""" """
if not isinstance(request.DATA, list): serializer = EncodedVideoSetDeserializer(data=request.DATA)
error_message = "Not a list: {0}".format(type(request.DATA)) if serializer.is_valid():
return Response(error_message, status=status.HTTP_400_BAD_REQUEST) serializer.save()
invalid_videos = [] return Response("Success", status=status.HTTP_201_CREATED)
for item in request.DATA:
try:
instance = Video.objects.get(
edx_video_id=item.get("edx_video_id")
)
except Video.DoesNotExist:
instance = None
serializer = VideoSerializer(instance, data=item)
if serializer.is_valid():
serializer.save()
else:
invalid_videos.append((serializer.errors, item))
if invalid_videos:
return Response(invalid_videos, status=status.HTTP_400_BAD_REQUEST)
else: else:
return Response(status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class VideoDetail(generics.RetrieveUpdateDestroyAPIView): class VideoDetail(generics.RetrieveUpdateDestroyAPIView):
...@@ -62,4 +48,4 @@ class VideoDetail(generics.RetrieveUpdateDestroyAPIView): ...@@ -62,4 +48,4 @@ class VideoDetail(generics.RetrieveUpdateDestroyAPIView):
""" """
lookup_field = "edx_video_id" lookup_field = "edx_video_id"
queryset = Video.objects.all() queryset = Video.objects.all()
serializer_class = VideoSerializer serializer_class = VideoSerializer
\ No newline at end of file
django-nose==1.2 django-nose==1.2
coverage==3.7.1 coverage==3.7.1
mock==1.0.1 mock==1.0.1
\ No newline at end of file django-debug-toolbar==1.2.1
from django.conf.urls import patterns, include, url from django.conf.urls import patterns, include, url
from rest_framework.urlpatterns import format_suffix_patterns
from edxval import views
from django.contrib import admin from django.contrib import admin
admin.autodiscover() admin.autodiscover()
urlpatterns = patterns('', urlpatterns = patterns('',
url(r'^edxval/', include('edxval.urls')), url(r'^edxval/video/$', views.VideoList.as_view(),
name="video_view"),
url(r'^edxval/video/(?P<edx_video_id>\w+)',
views.VideoDetail.as_view(),
name="video_detail_view"),
url(r'^admin/', include(admin.site.urls)), url(r'^admin/', include(admin.site.urls)),
) )
\ No newline at end of file
urlpatterns = format_suffix_patterns(urlpatterns)
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