Commit 2bb8cd01 by christopher lee

Initialized django project for VAL

Created models for Video, EncodedVideo, and Profile. Added
function to return a serialized video object, with it's
associated EncodedVideo objects.

Other Notes:
-Added djangorestframework==2.3.5
-Added Travis
-Added mock==1.0.1
-Added django-nose==1.2
-Added coverage==3.7.1
parent 15736e0d
*.py[cod]
*.db
# C extensions
*.so
# Packages
*.egg
*.egg-info
dist
build
eggs
parts
bin
var
sdist
develop-eggs
.installed.cfg
lib64
__pycache__
# Various common editor backups
*.orig
.*.swp
# Installer logs
pip-log.txt
# Unit test / coverage reports
.coverage
.tox
nosetests.xml
htmlcov
coverage.xml
# Mr Developer
.mr.developer.cfg
.project
.pydevproject
# Sass/Codekit
.sass-cache/
config.codekit
# some mac thing
.DS_Store
# PyCharm
.idea
# node
node_modules
npm-debug.log
coverage
# tim-specific
ora2db
storage/*
openassessment/xblock/static/js/fixtures/*.html
# logging
logs/*/*.log*
# Vagrant
.vagrant
from django.contrib import admin
from .models import Video, Profile, EncodedVideo
admin.site.register(Video)
admin.site.register(Profile)
admin.site.register(EncodedVideo)
# -*- coding: utf-8 -*-
"""
The internal API for VAL
"""
import logging
from edxval.models import Video
from edxval.serializers import EncodedVideoSetSerializer
logger = logging.getLogger(__name__)
class ValError(Exception):
"""
An error that occurs during VAL actions.
This error is raised when the VAL API cannot perform a requested
action.
"""
pass
class ValInternalError(ValError):
"""
An error internal to the VAL API has occurred.
This error is raised when an error occurs that is not caused by incorrect
use of the API, but rather internal implementation of the underlying
services.
"""
pass
class ValVideoNotFoundError(ValError):
"""
This error is raised when a video is not found
If a state is specified in a call to the API that results in no matching
entry in database, this error may be raised.
"""
pass
def get_video_info(edx_video_id, location=None):
"""
Retrieves all encoded videos of a video found with given video edx_video_id
Args:
location (str): geographic locations used determine CDN
edx_video_id (str): id for video content.
Returns:
result (dict): Deserialized Video Object with related field EncodedVideo
Returns all the Video object fields, and it's related EncodedVideo
objects in a list.
{
edx_video_id: ID of the video
duration: Length of video in seconds
client_title: human readable ID
encoded_video: a list of EncodedVideo dicts
url: url of the video
file_size: size of the video in bytes
profile: a dict of encoding details
profile_name: ID of the profile
extension: 3 letter extension of video
width: horizontal pixel resolution
height: vertical pixel resolution
}
Raises:
ValVideoNotFoundError: Raised if video doesn't exist
ValInternalError: Raised for unknown errors
Example:
Given one EncodedVideo with edx_video_id "thisis12char-thisis7"
>>>
>>> get_video_info("thisis12char-thisis7",location)
Returns (dict):
>>>{
>>> 'edx_video_id': u'thisis12char-thisis7',
>>> 'duration': 111.0,
>>> 'client_title': u'Thunder Cats S01E01',
>>> 'encoded_video': [
>>> {
>>> 'url': u'http://www.meowmix.com',
>>> 'file_size': 25556,
>>> 'bitrate': 9600,
>>> 'profile': {
>>> 'profile_name': u'mobile',
>>> 'extension': u'avi',
>>> 'width': 100,
>>> 'height': 101
>>> }
>>> },
>>> ]
>>>}
"""
try:
v = Video.objects.get(edx_video_id=edx_video_id)
result = EncodedVideoSetSerializer(v)
except Video.DoesNotExist:
error_message = u"Video not found for edx_video_id: {0}".format(edx_video_id)
raise ValVideoNotFoundError(error_message)
except Exception:
error_message = u"Could not get edx_video_id: {0}".format(edx_video_id)
logger.exception(error_message)
raise ValInternalError(error_message)
return result.data
\ No newline at end of file
"""
Django models for videos for Video Abstraction Layer (VAL)
"""
from django.db import models
class Profile(models.Model):
"""
Details for pre-defined encoding format
"""
profile_name = models.CharField(max_length=50, unique=True)
extension = models.CharField(max_length=10)
width = models.PositiveIntegerField()
height = models.PositiveIntegerField()
def __repr__(self):
return (
u"Profile(profile_name={0.profile_name})"
).format(self)
def __unicode__(self):
return repr(self)
class Video(models.Model):
"""
Model for a Video group with the same content.
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()
def __repr__(self):
return (
u"Video(client_title={0.client_title}, duration={0.duration})"
).format(self)
def __unicode__(self):
return repr(self)
class CourseVideos(models.Model):
"""
Model for the course_id associated with the video content.
Every course-semester has a unique course_id. A video can be paired with multiple
course_id's but each pair is unique together.
"""
course_id = models.CharField(max_length=255)
video = models.ForeignKey(Video)
class Meta:
unique_together = ("course_id", "video")
class EncodedVideo(models.Model):
"""
Video/encoding pair
"""
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
url = models.URLField(max_length=200)
file_size = models.PositiveIntegerField()
bitrate = models.PositiveIntegerField()
profile = models.ForeignKey(Profile, related_name="+")
video = models.ForeignKey(Video, related_name="encoded_videos")
def __repr__(self):
return (
u"EncodedVideo(video={0.video.client_title}, "
u"profile={0.profile.profile_name})"
).format(self)
def __unicode__(self):
return repr(self)
\ No newline at end of file
"""
Serializers for Video Abstraction Layer
"""
from rest_framework import serializers
from django.core.validators import MinValueValidator
from edxval.models import Profile
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 ProfileSerializer(serializers.ModelSerializer):
class Meta:
model = Profile
fields = (
"profile_name",
"extension",
"width",
"height"
)
class OnlyEncodedVideoSerializer(serializers.Serializer):
"""
Used to serialize the EncodedVideo fir the EncodedVideoSetSerializer
"""
url = serializers.URLField(max_length=200)
file_size = serializers.IntegerField(validators=[MinValueValidator(1)])
bitrate = serializers.IntegerField(validators=[MinValueValidator(1)])
profile = ProfileSerializer()
class EncodedVideoSetSerializer(serializers.Serializer):
"""
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()
# Django settings for edxval project.
DEBUG = True
TEMPLATE_DEBUG = DEBUG
ADMINS = (
# ('Your Name', 'your_email@example.com'),
)
MANAGERS = ADMINS
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
'NAME': 'video.db', # Or path to database file if using sqlite3.
'USER': '', # Not used with sqlite3.
'PASSWORD': '', # Not used with sqlite3.
'HOST': '', # Set to empty string for localhost. Not used with sqlite3.
'PORT': '', # Set to empty string for default. Not used with sqlite3.
}
}
# Hosts/domain names that are valid for this site; required if DEBUG is False
# See https://docs.djangoproject.com/en/1.4/ref/settings/#allowed-hosts
ALLOWED_HOSTS = []
# Local time zone for this installation. Choices can be found here:
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
# although not all choices may be available on all operating systems.
# In a Windows environment this must be set to your system time zone.
TIME_ZONE = 'America/New_York'
# Language code for this installation. All choices can be found here:
# http://www.i18nguy.com/unicode/language-identifiers.html
LANGUAGE_CODE = 'en-us'
SITE_ID = 1
# If you set this to False, Django will make some optimizations so as not
# to load the internationalization machinery.
USE_I18N = True
# If you set this to False, Django will not format dates, numbers and
# calendars according to the current locale.
USE_L10N = True
# If you set this to False, Django will not use timezone-aware datetimes.
USE_TZ = True
# Absolute filesystem path to the directory that will hold user-uploaded files.
# Example: "/home/media/media.lawrence.com/media/"
MEDIA_ROOT = ''
# URL that handles the media served from MEDIA_ROOT. Make sure to use a
# trailing slash.
# Examples: "http://media.lawrence.com/media/", "http://example.com/media/"
MEDIA_URL = ''
# Absolute path to the directory static files should be collected to.
# Don't put anything in this directory yourself; store your static files
# in apps' "static/" subdirectories and in STATICFILES_DIRS.
# Example: "/home/media/media.lawrence.com/static/"
STATIC_ROOT = ''
# URL prefix for static files.
# Example: "http://media.lawrence.com/static/"
STATIC_URL = '/static/'
# Additional locations of static files
STATICFILES_DIRS = (
# Put strings here, like "/home/html/static" or "C:/www/django/static".
# Always use forward slashes, even on Windows.
# Don't forget to use absolute paths, not relative paths.
)
# List of finder classes that know how to find static files in
# various locations.
STATICFILES_FINDERS = (
'django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
# 'django.contrib.staticfiles.finders.DefaultStorageFinder',
)
# Make this unique, and don't share it with anybody.
SECRET_KEY = ')5n@d^*763&##4c(vtzg6&%d7^yiee@5zk-n$rw7djcmz+4u4n'
# List of callables that know how to import templates from various sources.
TEMPLATE_LOADERS = (
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
# 'django.template.loaders.eggs.Loader',
)
MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
# Uncomment the next line for simple clickjacking protection:
# 'django.middleware.clickjacking.XFrameOptionsMiddleware',
)
ROOT_URLCONF = 'edxval.urls'
# Python dotted path to the WSGI application used by Django's runserver.
WSGI_APPLICATION = 'edxval.wsgi.application'
TEMPLATE_DIRS = (
# Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
# Always use forward slashes, even on Windows.
# Don't forget to use absolute paths, not relative paths.
)
INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.messages',
'django.contrib.staticfiles',
'edxval',
'django_nose',
# Uncomment the next line to enable the admin:
'django.contrib.admin',
# Uncomment the next line to enable admin documentation:
# 'django.contrib.admindocs',
)
TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'
TEST_APPS = ('edxval',)
NOSE_ARGS = [
'--with-coverage',
'--cover-package=' + ",".join(TEST_APPS),
'--cover-branches',
'--cover-erase',
]
# A sample logging configuration. The only tangible logging
# performed by this configuration is to send an email to
# the site admins on every HTTP 500 error when DEBUG=False.
# See http://docs.djangoproject.com/en/dev/topics/logging for
# more details on how to customize your logging configuration.
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'filters': {
'require_debug_false': {
'()': 'django.utils.log.RequireDebugFalse'
}
},
'handlers': {
'mail_admins': {
'level': 'ERROR',
'filters': ['require_debug_false'],
'class': 'django.utils.log.AdminEmailHandler'
}
},
'loggers': {
'django.request': {
'handlers': ['mail_admins'],
'level': 'ERROR',
'propagate': True,
},
}
}
\ No newline at end of file
# -*- coding: utf-8 -*-
EDX_VIDEO_ID = "thisis12char-thisis7"
ENCODED_VIDEO_DICT_MOBILE = dict(
url="http://www.meowmix.com",
file_size=25556,
bitrate=9600,
)
ENCODED_VIDEO_DICT_DESKTOP = dict(
url="http://www.meowmagic.com",
file_size=25556,
bitrate=9600,
)
ENCODED_VIDEO_DICT_NEGATIVE_FILESIZE = dict(
url="http://www.meowmix.com",
file_size=-25556,
bitrate=9600,
)
ENCODED_VIDEO_DICT_NEGATIVE_BITRATE = dict(
url="http://www.meowmix.com",
file_size=25556,
bitrate=-9600,
)
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
)
PROFILE_DICT_NON_LATIN = dict(
profile_name=u"배고파",
extension="mew",
width=100,
height=300
)
VIDEO_DICT_CATS = dict(
client_title="Thunder Cats S01E01",
duration=111.00,
edx_video_id="thisis12char-thisis7",
)
VIDEO_DICT_NEGATIVE_DURATION = dict(
client_title="Thunder Cats S01E01",
duration=-111,
edx_video_id="thisis12char-thisis7",
)
\ No newline at end of file
# -*- coding: utf-8 -*-
"""
Tests for the API for Video Abstraction Layer
"""
import mock
from django.test import TestCase
from django.db import DatabaseError
from edxval.models import Profile, Video, EncodedVideo
from edxval import api as api
from edxval.serializers import EncodedVideoSetSerializer
from edxval.tests import constants
class GetVideoInfoTest(TestCase):
def setUp(self):
"""
Creates EncodedVideo objects in database
"""
Profile.objects.create(**constants.PROFILE_DICT_MOBILE)
Profile.objects.create(**constants.PROFILE_DICT_DESKTOP)
Video.objects.create(**constants.VIDEO_DICT_CATS)
EncodedVideo.objects.create(
video=Video.objects.get(
edx_video_id=constants.VIDEO_DICT_CATS.get("edx_video_id")
),
profile=Profile.objects.get(profile_name="mobile"),
**constants.ENCODED_VIDEO_DICT_MOBILE
)
EncodedVideo.objects.create(
video=Video.objects.get(
edx_video_id=constants.VIDEO_DICT_CATS.get("edx_video_id")
),
profile=Profile.objects.get(profile_name="desktop"),
**constants.ENCODED_VIDEO_DICT_DESKTOP
)
def test_get_video_found(self):
"""
Tests for successful video request
"""
self.assertIsNotNone(api.get_video_info(constants.EDX_VIDEO_ID))
def test_no_such_video(self):
"""
Tests searching for a video that does not exist
"""
with self.assertRaises(api.ValVideoNotFoundError):
api.get_video_info("non_existing-video__")
def test_unicode_input(self):
"""
Tests if unicode inputs are handled correctly
"""
with self.assertRaises(api.ValVideoNotFoundError):
api.get_video_info(u"๓ﻉѻฝ๓ٱซ")
@mock.patch.object(EncodedVideoSetSerializer, '__init__')
def test_force_internal_error(self, mock_init):
"""
Tests to see if an unknown error will be handled
"""
mock_init.side_effect = Exception("Mock error")
with self.assertRaises(api.ValInternalError):
api.get_video_info(constants.EDX_VIDEO_ID)
@mock.patch.object(Video.objects, 'get')
def test_force_database_error(self, mock_get):
"""
Tests to see if an database error will be handled
"""
mock_get.side_effect = DatabaseError("DatabaseError")
with self.assertRaises(api.ValInternalError):
api.get_video_info(constants.EDX_VIDEO_ID)
\ No newline at end of file
"""
Tests for Video Abstraction Layer models
"""
\ No newline at end of file
# -*- coding: utf-8 -*-
"""
Tests the serializers for the Video Abstraction Layer
"""
from django.test import TestCase
from edxval.serializers import (
OnlyEncodedVideoSerializer,
EncodedVideoSetSerializer,
ProfileSerializer
)
from edxval.models import Profile
from edxval.tests import constants
class SerializerTests(TestCase):
"""
Tests the Serializers
"""
def setUp(self):
"""
Creates EncodedVideo objects in database
"""
Profile.objects.create(**constants.PROFILE_DICT_MOBILE)
Profile.objects.create(**constants.PROFILE_DICT_NON_LATIN)
def test_negative_fields(self):
"""
Tests negative inputs for a serializer
Tests negative inputs for bitrate, file_size in EncodedVideo,
and duration in Video
"""
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.")
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.")
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.")
def test_unicode_inputs(self):
"""
Tests if the serializers can accept non-latin chars
"""
self.assertIsNotNone(
ProfileSerializer(Profile.objects.get(profile_name="배고파"))
)
\ No newline at end of file
from django.conf.urls import patterns, include, url
# Uncomment the next two lines to enable the admin:
from django.contrib import admin
admin.autodiscover()
urlpatterns = patterns('',
# Examples:
# url(r'^$', 'edxval.views.home', name='home'),
# url(r'^edxval/', include('edxval.foo.urls')),
# Uncomment the admin/doc line below to enable admin documentation:
# url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
# Uncomment the next line to enable the admin:
url(r'^admin/', include(admin.site.urls)),
)
\ No newline at end of file
"""
WSGI config for edxval project.
This module contains the WSGI application used by Django's development server
and any production WSGI deployments. It should expose a module-level variable
named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover
this application via the ``WSGI_APPLICATION`` setting.
Usually you will have the standard Django WSGI application here, but it also
might make sense to replace the whole Django WSGI application with a custom one
that later delegates to the Django one. For example, you could introduce WSGI
middleware here, or combine a Django application with an application of another
framework.
"""
import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "edxval.settings")
# This application object is used by any WSGI server configured to use this
# file. This includes Django's development server, if the WSGI_APPLICATION
# setting points here.
from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()
# Apply WSGI middleware here.
# from helloworld.wsgi import HelloWorldApplication
# application = HelloWorldApplication(application)
#!/usr/bin/env python
import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "edxval.settings")
from django.core.management import execute_from_command_line
execute_from_command_line(sys.argv)
django>=1.4,<1.5
djangorestframework==2.3.5
\ No newline at end of file
django-nose==1.2
coverage==3.7.1
mock==1.0.1
\ No newline at end of file
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