Commit dd811f85 by Dave St.Germain

Added subtitle model and API

parent df4728fc
...@@ -3,9 +3,9 @@ Admin file for django app edxval. ...@@ -3,9 +3,9 @@ Admin file for django app edxval.
""" """
from django.contrib import admin from django.contrib import admin
from .models import Video, Profile, EncodedVideo from .models import Video, Profile, EncodedVideo, Subtitle
admin.site.register(Video) admin.site.register(Video)
admin.site.register(Profile) admin.site.register(Profile)
admin.site.register(EncodedVideo) admin.site.register(EncodedVideo)
admin.site.register(Subtitle)
...@@ -139,6 +139,11 @@ def get_video_info(edx_video_id, location=None): # pylint: disable=W0613 ...@@ -139,6 +139,11 @@ def get_video_info(edx_video_id, location=None): # pylint: disable=W0613
extension: 3 letter extension of video extension: 3 letter extension of video
width: horizontal pixel resolution width: horizontal pixel resolution
height: vertical pixel resolution height: vertical pixel resolution
subtitles: a list of Subtitle dicts
fmt: file format (SRT or SJSON)
language: language code
content_url: url of file
url: api url to subtitle
} }
Raises: Raises:
...@@ -174,4 +179,4 @@ def get_video_info(edx_video_id, location=None): # pylint: disable=W0613 ...@@ -174,4 +179,4 @@ def get_video_info(edx_video_id, location=None): # pylint: disable=W0613
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 # pylint: disable=E1101 return result.data # pylint: disable=E1101
...@@ -29,6 +29,7 @@ invalid profile_name will be returned. ...@@ -29,6 +29,7 @@ invalid profile_name will be returned.
from django.db import models from django.db import models
from django.core.validators import MinValueValidator, RegexValidator from django.core.validators import MinValueValidator, RegexValidator
from django.core.urlresolvers import reverse
url_regex = r'^[a-zA-Z0-9\-]*$' url_regex = r'^[a-zA-Z0-9\-]*$'
...@@ -78,6 +79,9 @@ class Video(models.Model): ...@@ -78,6 +79,9 @@ class Video(models.Model):
client_video_id = models.CharField(max_length=255, db_index=True) client_video_id = models.CharField(max_length=255, db_index=True)
duration = models.FloatField(validators=[MinValueValidator(0)]) duration = models.FloatField(validators=[MinValueValidator(0)])
def __str__(self):
return self.edx_video_id
class CourseVideos(models.Model): class CourseVideos(models.Model):
""" """
...@@ -89,7 +93,7 @@ class CourseVideos(models.Model): ...@@ -89,7 +93,7 @@ class CourseVideos(models.Model):
course_id = models.CharField(max_length=255) course_id = models.CharField(max_length=255)
video = models.ForeignKey(Video) video = models.ForeignKey(Video)
class Meta: class Meta: # pylint: disable=C1001
""" """
course_id is listed first in this composite index course_id is listed first in this composite index
""" """
...@@ -108,3 +112,34 @@ class EncodedVideo(models.Model): ...@@ -108,3 +112,34 @@ class EncodedVideo(models.Model):
profile = models.ForeignKey(Profile, related_name="+") profile = models.ForeignKey(Profile, related_name="+")
video = models.ForeignKey(Video, related_name="encoded_videos") video = models.ForeignKey(Video, related_name="encoded_videos")
SUBTITLE_FORMATS = (
('srt', 'SubRip'),
('sjson', 'SRT JSON')
)
class Subtitle(models.Model):
"""
Subtitle for video
"""
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
video = models.ForeignKey(Video, related_name="subtitles")
fmt = models.CharField(max_length=20, db_index=True, choices=SUBTITLE_FORMATS)
language = models.CharField(max_length=8, db_index=True)
content = models.TextField()
def __str__(self):
return '%s Subtitle for %s' % (self.language, self.video)
def get_absolute_url(self):
return reverse('subtitle-content', args=[str(self.id)])
@property
def content_type(self):
if self.fmt == 'sjson':
return 'application/json'
else:
return 'text/plain'
...@@ -7,7 +7,7 @@ EncodedVideoSerializer which uses the profile_name as it's profile field. ...@@ -7,7 +7,7 @@ EncodedVideoSerializer which uses the profile_name as it's profile field.
from rest_framework import serializers from rest_framework import serializers
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from edxval.models import Profile, Video, EncodedVideo from edxval.models import Profile, Video, EncodedVideo, Subtitle
class ProfileSerializer(serializers.ModelSerializer): class ProfileSerializer(serializers.ModelSerializer):
...@@ -51,6 +51,41 @@ class EncodedVideoSerializer(serializers.ModelSerializer): ...@@ -51,6 +51,41 @@ class EncodedVideoSerializer(serializers.ModelSerializer):
return data.get('profile', None) return data.get('profile', None)
class SubtitleSerializer(serializers.HyperlinkedModelSerializer):
"""
Serializer for Subtitle objects
"""
content_url = serializers.CharField(source='get_absolute_url', read_only=True)
content = serializers.CharField(write_only=True)
def validate_content(self, attrs, source):
"""
Validate that the subtitle is in the correct format
"""
value = attrs[source]
if attrs.get('fmt') == 'sjson':
import json
try:
loaded = json.loads(value)
except ValueError:
raise serializers.ValidationError("Not in JSON format")
else:
attrs[source] = json.dumps(loaded)
return attrs
class Meta: # pylint: disable=C1001, C0111
model = Subtitle
lookup_field = "id"
fields = (
"fmt",
"language",
"id",
"content_url",
"url",
"content"
)
class VideoSerializer(serializers.HyperlinkedModelSerializer): class VideoSerializer(serializers.HyperlinkedModelSerializer):
""" """
Serializer for Video object Serializer for Video object
...@@ -58,6 +93,7 @@ class VideoSerializer(serializers.HyperlinkedModelSerializer): ...@@ -58,6 +93,7 @@ class VideoSerializer(serializers.HyperlinkedModelSerializer):
encoded_videos takes a list of dicts EncodedVideo data. encoded_videos takes a list of dicts EncodedVideo data.
""" """
encoded_videos = EncodedVideoSerializer(many=True, allow_add_remove=True) encoded_videos = EncodedVideoSerializer(many=True, allow_add_remove=True)
subtitles = SubtitleSerializer(many=True, allow_add_remove=True)
class Meta: # pylint: disable=C0111 class Meta: # pylint: disable=C0111
model = Video model = Video
......
...@@ -18,4 +18,13 @@ urlpatterns = patterns( ...@@ -18,4 +18,13 @@ urlpatterns = patterns(
views.VideoDetail.as_view(), views.VideoDetail.as_view(),
name="video-detail" name="video-detail"
), ),
url(
r'^edxval/subtitle/(?P<id>[\d]+)$',
views.SubtitleDetail.as_view(),
name="subtitle-detail"
),
url(r'^edxval/subtitle/(?P<subtitle_id>[\d]+)/content$',
views.get_subtitle,
name="subtitle-content"
),
) )
...@@ -3,11 +3,14 @@ Views file for django app edxval. ...@@ -3,11 +3,14 @@ Views file for django app edxval.
""" """
from rest_framework import generics from rest_framework import generics
from django.http import HttpResponse
from django.views.decorators.http import last_modified
from edxval.models import Video, Profile from edxval.models import Video, Profile, Subtitle
from edxval.serializers import ( from edxval.serializers import (
VideoSerializer, VideoSerializer,
ProfileSerializer ProfileSerializer,
SubtitleSerializer
) )
...@@ -36,3 +39,22 @@ class VideoDetail(generics.RetrieveUpdateDestroyAPIView): ...@@ -36,3 +39,22 @@ 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
class SubtitleDetail(generics.RetrieveUpdateDestroyAPIView):
"""
Gets a subtitle instance given its id
"""
lookup_field = "id"
queryset = Subtitle.objects.all()
serializer_class = SubtitleSerializer
@last_modified(last_modified_func=lambda request, subtitle_id: Subtitle.objects.get(pk=subtitle_id).modified)
def get_subtitle(request, subtitle_id):
"""
Return content of subtitle by id
"""
sub = Subtitle.objects.get(pk=subtitle_id)
response = HttpResponse(sub.content, content_type=sub.content_type)
return response
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