Skip to content

Commit

Permalink
added POST/GET for video models
Browse files Browse the repository at this point in the history
Initial part of the upload portion of the API. Video Models can
be uploaded via POST as a list of dictionaries. Matching entries
will be updated and new entries will be created. Errors are
returned in a list of tuples with the message and input. Single
entries can be returned by edx_video_id via GET.

Other notes:
-Django Rest Framework upgraded for APIView 2.3.5->2.3.14
-Field "client_title" -> "client_video_id" in the Video Model
  • Loading branch information
christopher lee committed Aug 4, 2014
1 parent 2bb8cd0 commit 7a91043
Show file tree
Hide file tree
Showing 16 changed files with 1,071 additions and 44 deletions.
9 changes: 9 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[run]
branch = True

[report]
include = edxval/*
omit =
**/__init__.py
**/tests/*
**/settings.py
11 changes: 11 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
language: python
python:
- "2.7"
install:
- "pip install -r requirements.txt"
- "pip install -r test-requirements.txt"
- "pip install coveralls"
script:
- "python manage.py test"
after_success:
coveralls
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Christopher Lee <[email protected]>
671 changes: 671 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions edxval/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def get_video_info(edx_video_id, location=None):
{
edx_video_id: ID of the video
duration: Length of video in seconds
client_title: human readable ID
client_video_id: client ID of video
encoded_video: a list of EncodedVideo dicts
url: url of the video
file_size: size of the video in bytes
Expand All @@ -82,7 +82,7 @@ def get_video_info(edx_video_id, location=None):
>>>{
>>> 'edx_video_id': u'thisis12char-thisis7',
>>> 'duration': 111.0,
>>> 'client_title': u'Thunder Cats S01E01',
>>> 'client_video_id': u'Thunder Cats S01E01',
>>> 'encoded_video': [
>>> {
>>> 'url': u'http://www.meowmix.com',
Expand Down
21 changes: 16 additions & 5 deletions edxval/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"""

from django.db import models
from django.core.validators import MinValueValidator, RegexValidator


class Profile(models.Model):
Expand Down Expand Up @@ -30,13 +31,23 @@ class Video(models.Model):
A video can have multiple formats. This model is the collection of those
videos with fields that do not change across formats.
"""
edx_video_id = models.CharField(max_length=50, unique=True)
client_title = models.CharField(max_length=255, db_index=True)
duration = models.FloatField()
edx_video_id = models.CharField(
max_length=50,
unique=True,
validators=[
RegexValidator(
regex='^[a-zA-Z0-9\-]*$',
message='edx_video_id has invalid characters',
code='invalid edx_video_id'
),
]
)
client_video_id = models.CharField(max_length=255, db_index=True)
duration = models.FloatField(validators=[MinValueValidator(0)])

def __repr__(self):
return (
u"Video(client_title={0.client_title}, duration={0.duration})"
u"Video(client_video_id={0.client_video_id}, duration={0.duration})"
).format(self)

def __unicode__(self):
Expand Down Expand Up @@ -72,7 +83,7 @@ class EncodedVideo(models.Model):

def __repr__(self):
return (
u"EncodedVideo(video={0.video.client_title}, "
u"EncodedVideo(video={0.video.client_video_id}, "
u"profile={0.profile.profile_name})"
).format(self)

Expand Down
61 changes: 46 additions & 15 deletions edxval/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,37 @@
Serializers for Video Abstraction Layer
"""
from rest_framework import serializers
from django.core.validators import MinValueValidator

from edxval.models import Profile
from edxval.models import Profile, Video, EncodedVideo


class VideoSerializer(serializers.Serializer):
edx_video_id = serializers.CharField(required=True, max_length=50)
duration = serializers.FloatField()
client_title = serializers.CharField(max_length=255)
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 ProfileSerializer(serializers.ModelSerializer):
class Meta:
Expand All @@ -24,21 +45,31 @@ class Meta:
)


class OnlyEncodedVideoSerializer(serializers.Serializer):
class OnlyEncodedVideoSerializer(serializers.ModelSerializer):
"""
Used to serialize the EncodedVideo fir the EncodedVideoSetSerializer
Used to serialize the EncodedVideo for the EncodedVideoSetSerializer
"""
url = serializers.URLField(max_length=200)
file_size = serializers.IntegerField(validators=[MinValueValidator(1)])
bitrate = serializers.IntegerField(validators=[MinValueValidator(1)])
profile = ProfileSerializer()
profile = ProfileSerializer(required=False)
class Meta:
model = EncodedVideo
fields = (
"url",
"file_size",
"bitrate"
)


class EncodedVideoSetSerializer(serializers.Serializer):
class EncodedVideoSetSerializer(serializers.ModelSerializer):
"""
Used to serialize a list of EncodedVideo objects it's foreign key Video Object.
"""
edx_video_id = serializers.CharField(max_length=50)
client_title = serializers.CharField(max_length=255)
duration = serializers.FloatField(validators=[MinValueValidator(1)])
encoded_videos = OnlyEncodedVideoSerializer()

class Meta:
model = Video
fields = (
"duration",
"client_video_id"
)

1 change: 1 addition & 0 deletions edxval/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@
'django.contrib.staticfiles',
'edxval',
'django_nose',
'rest_framework',
# Uncomment the next line to enable the admin:
'django.contrib.admin',
# Uncomment the next line to enable admin documentation:
Expand Down
77 changes: 75 additions & 2 deletions edxval/tests/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,86 @@
height=300
)
VIDEO_DICT_CATS = dict(
client_title="Thunder Cats S01E01",
client_video_id="Thunder Cats S01E01",
duration=111.00,
edx_video_id="thisis12char-thisis7",
)

VIDEO_DICT_LION = dict(
client_video_id="Lolcat",
duration=111.00,
edx_video_id="caw",
)
VIDEO_DICT_LION2 = dict(
client_video_id="Lolcat",
duration=122.00,
edx_video_id="caw",
)

VIDEO_DICT_TIGERS_BEARS = [
dict(
client_video_id="Tipsy Tiger",
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(
client_video_id="Gaggling gopher",
duration=111.00,
edx_video_id="gg",
),
dict(
client_video_id="Gaggling gopher",
duration=111.00,
edx_video_id="gg",
),
]

VIDEO_DICT_NEGATIVE_DURATION = dict(
client_title="Thunder Cats S01E01",
client_video_id="Thunder Cats S01E01",
duration=-111,
edx_video_id="thisis12char-thisis7",
)

VIDEO_DICT_INVALID_ID = dict(
client_video_id="SuperSloth",
duration=42,
edx_video_id="sloppy/sloth!!"
)

VIDEO_DICT_NON_LATIN_TITLE = dict(
client_video_id="배고픈 햄스터",
duration=42,
edx_video_id="ID"
)

VIDEO_DICT_NON_LATIN_ID = dict(
client_video_id="Hungry Hamster",
duration=42,
edx_video_id="밥줘"
)
3 changes: 3 additions & 0 deletions edxval/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@

class GetVideoInfoTest(TestCase):

#TODO When upload portion is finished, do not forget to create tests for validating
#TODO regex for models. Currently, objects are created manually and validators
#TODO are not triggered.
def setUp(self):
"""
Creates EncodedVideo objects in database
Expand Down
35 changes: 26 additions & 9 deletions edxval/tests/test_serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
from edxval.serializers import (
OnlyEncodedVideoSerializer,
EncodedVideoSetSerializer,
ProfileSerializer
ProfileSerializer,
VideoSerializer
)
from edxval.models import Profile
from edxval.tests import constants
Expand All @@ -25,30 +26,46 @@ def setUp(self):
Profile.objects.create(**constants.PROFILE_DICT_MOBILE)
Profile.objects.create(**constants.PROFILE_DICT_NON_LATIN)

def test_negative_fields(self):
def test_negative_fields_only_encoded_video(self):
"""
Tests negative inputs for a serializer
Tests negative inputs for OnlyEncodedSerializer
Tests negative inputs for bitrate, file_size in EncodedVideo,
and duration in Video
Tests negative inputs for bitrate, file_size in EncodedVideo
"""
a = OnlyEncodedVideoSerializer(
data=constants.ENCODED_VIDEO_DICT_NEGATIVE_BITRATE).errors
self.assertEqual(a.get('bitrate')[0],
u"Ensure this value is greater than or equal to 1.")
u"Ensure this value is greater than or equal to 0.")
b = OnlyEncodedVideoSerializer(
data=constants.ENCODED_VIDEO_DICT_NEGATIVE_FILESIZE).errors
self.assertEqual(b.get('file_size')[0],
u"Ensure this value is greater than or equal to 1.")
u"Ensure this value is greater than or equal to 0.")

def test_negative_fields_video_set(self):
"""
Tests negative inputs for EncodedVideoSetSerializer
Tests negative inputs for duration in model Video
"""
c = EncodedVideoSetSerializer(
data=constants.VIDEO_DICT_NEGATIVE_DURATION).errors
self.assertEqual(c.get('duration')[0],
u"Ensure this value is greater than or equal to 1.")
u"Ensure this value is greater than or equal to 0.")

def test_unicode_inputs(self):
"""
Tests if the serializers can accept non-latin chars
"""
self.assertIsNotNone(
ProfileSerializer(Profile.objects.get(profile_name="배고파"))
)
)

def test_invalid_edx_video_id(self):
"""
Test the Video model regex validation for edx_video_id field
"""
error = VideoSerializer(data=constants.VIDEO_DICT_INVALID_ID).errors
message = error.get("edx_video_id")[0]
self.assertEqual(
message,
u"edx_video_id has invalid characters")
Loading

0 comments on commit 7a91043

Please sign in to comment.