Commit d3000dd2 by Dave St.Germain

Expose course_id to the API

parent 01f3b9c1
......@@ -3,9 +3,10 @@ Admin file for django app edxval.
"""
from django.contrib import admin
from .models import Video, Profile, EncodedVideo, Subtitle
from .models import Video, Profile, EncodedVideo, Subtitle, CourseVideos
admin.site.register(Video)
admin.site.register(Profile)
admin.site.register(EncodedVideo)
admin.site.register(Subtitle)
admin.site.register(CourseVideos)
......@@ -170,7 +170,7 @@ def get_video_info(edx_video_id, location=None): # pylint: disable=W0613
}
"""
try:
video = Video.objects.get(edx_video_id=edx_video_id)
video = Video.objects.prefetch_related("encoded_videos", "courses").get(edx_video_id=edx_video_id)
result = VideoSerializer(video)
except Video.DoesNotExist:
error_message = u"Video not found for edx_video_id: {0}".format(edx_video_id)
......@@ -180,3 +180,10 @@ def get_video_info(edx_video_id, location=None): # pylint: disable=W0613
logger.exception(error_message)
raise ValInternalError(error_message)
return result.data # pylint: disable=E1101
def get_videos_for_course(course_id):
"""
Returns an iterator of videos for the given course id
"""
videos = Video.objects.filter(courses__course_id=course_id)
return (VideoSerializer(video).data for video in videos)
......@@ -79,6 +79,9 @@ class Video(models.Model):
client_video_id = models.CharField(max_length=255, db_index=True)
duration = models.FloatField(validators=[MinValueValidator(0)])
def get_absolute_url(self):
return reverse('video-detail', args=[self.edx_video_id])
def __str__(self):
return self.edx_video_id
......@@ -91,7 +94,7 @@ class CourseVideos(models.Model):
course_id's but each pair is unique together.
"""
course_id = models.CharField(max_length=255)
video = models.ForeignKey(Video)
video = models.ForeignKey(Video, related_name='courses')
class Meta: # pylint: disable=C1001
"""
......@@ -99,6 +102,9 @@ class CourseVideos(models.Model):
"""
unique_together = ("course_id", "video")
def __str__(self):
return '%s for %s' % (self.video, self.course_id)
class EncodedVideo(models.Model):
"""
......
......@@ -7,7 +7,7 @@ EncodedVideoSerializer which uses the profile_name as it's profile field.
from rest_framework import serializers
from django.core.exceptions import ValidationError
from edxval.models import Profile, Video, EncodedVideo, Subtitle
from edxval.models import Profile, Video, EncodedVideo, Subtitle, CourseVideos
class ProfileSerializer(serializers.ModelSerializer):
......@@ -84,6 +84,18 @@ class SubtitleSerializer(serializers.ModelSerializer):
)
class CourseSerializer(serializers.RelatedField):
"""
Field for CourseVideos
"""
def to_native(self, value):
return value.course_id
def from_native(self, data):
if data:
return CourseVideos(course_id=data)
class VideoSerializer(serializers.ModelSerializer):
"""
Serializer for Video object
......@@ -92,12 +104,20 @@ class VideoSerializer(serializers.ModelSerializer):
"""
encoded_videos = EncodedVideoSerializer(many=True, allow_add_remove=True)
subtitles = SubtitleSerializer(many=True, allow_add_remove=True, required=False)
courses = CourseSerializer(many=True, read_only=False)
url = serializers.SerializerMethodField('get_url')
class Meta: # pylint: disable=C0111
model = Video
lookup_field = "edx_video_id"
exclude = ('id',)
def get_url(self, obj):
"""
Return relative url for the object
"""
return obj.get_absolute_url()
def restore_fields(self, data, files):
"""
Overridden function used to check against duplicate profile names.
......@@ -111,6 +131,7 @@ class VideoSerializer(serializers.ModelSerializer):
if data is not None and not isinstance(data, dict):
self._errors['non_field_errors'] = ['Invalid data']
return None
try:
profiles = [ev["profile"] for ev in data.get("encoded_videos", [])]
if len(profiles) != len(set(profiles)):
......
......@@ -12,7 +12,7 @@ from django.core.exceptions import ValidationError
from rest_framework import status
from ddt import ddt, data
from edxval.models import Profile, Video, EncodedVideo
from edxval.models import Profile, Video, EncodedVideo, CourseVideos
from edxval import api as api
from edxval.api import ValCannotCreateError
from edxval.serializers import VideoSerializer
......@@ -123,7 +123,7 @@ class GetVideoInfoTest(TestCase):
"""
Profile.objects.create(**constants.PROFILE_DICT_MOBILE)
Profile.objects.create(**constants.PROFILE_DICT_DESKTOP)
Video.objects.create(**constants.VIDEO_DICT_FISH)
video = Video.objects.create(**constants.VIDEO_DICT_FISH)
EncodedVideo.objects.create(
video=Video.objects.get(
edx_video_id=constants.VIDEO_DICT_FISH.get("edx_video_id")
......@@ -138,6 +138,8 @@ class GetVideoInfoTest(TestCase):
profile=Profile.objects.get(profile_name="desktop"),
**constants.ENCODED_VIDEO_DICT_DESKTOP
)
self.course_id = 'test-course'
CourseVideos.objects.create(video=video, course_id=self.course_id)
def test_get_video_found(self):
"""
......@@ -149,6 +151,16 @@ class GetVideoInfoTest(TestCase):
)
)
def test_get_videos_for_course(self):
"""
Tests retrieving videos for a course id
"""
videos = list(api.get_videos_for_course(self.course_id))
self.assertEqual(len(videos), 1)
self.assertEqual(videos[0]['edx_video_id'], constants.VIDEO_DICT_FISH['edx_video_id'])
videos = list(api.get_videos_for_course('unknown'))
self.assertEqual(len(videos), 0)
def test_no_such_video(self):
"""
Tests searching for a video that does not exist
......@@ -177,7 +189,7 @@ class GetVideoInfoTest(TestCase):
constants.VIDEO_DICT_FISH.get("edx_video_id")
)
@mock.patch.object(Video.objects, 'get')
@mock.patch.object(Video, '__init__')
def test_force_database_error(self, mock_get):
"""
Tests to see if an database error will be handled
......@@ -225,7 +237,7 @@ class GetVideoInfoTestWithHttpCalls(APIAuthTestCase):
"""
Tests number of queries for a Video/EncodedVideo(1) pair
"""
with self.assertNumQueries(7):
with self.assertNumQueries(8):
api.get_video_info(constants.COMPLETE_SET_FISH.get("edx_video_id"))
def test_get_info_queries_for_one_encoded_video(self):
......@@ -237,7 +249,7 @@ class GetVideoInfoTestWithHttpCalls(APIAuthTestCase):
url, constants.COMPLETE_SET_STAR, format='json'
)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
with self.assertNumQueries(5):
with self.assertNumQueries(6):
api.get_video_info(constants.COMPLETE_SET_STAR.get("edx_video_id"))
def test_get_info_queries_for_only_video(self):
......@@ -249,6 +261,6 @@ class GetVideoInfoTestWithHttpCalls(APIAuthTestCase):
url, constants.VIDEO_DICT_ZEBRA, format='json'
)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
with self.assertNumQueries(3):
with self.assertNumQueries(4):
api.get_video_info(constants.VIDEO_DICT_ZEBRA.get("edx_video_id"))
......@@ -452,6 +452,35 @@ class VideoListTest(APIAuthTestCase):
errors.get("edx_video_id")[0],
"edx_video_id has invalid characters"
)
def test_add_course(self):
"""
Test adding a video with a course.
Test retrieving videos from course list view.
"""
url = reverse('video-list')
video = dict(**constants.VIDEO_DICT_ANIMAL)
course1 = 'animals/fish'
course2 = 'animals/birds'
video['courses'] = [course1, course2]
response = self.client.post(
url, video, format='json'
)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
videos = self.client.get("/edxval/video/").data
self.assertEqual(len(videos), 1)
self.assertEqual(videos[0]['courses'], [course1, course2])
url = reverse('course-video-list', kwargs={'course_id': course1})
videos = self.client.get(url).data
self.assertEqual(len(videos), 1)
self.assertEqual(videos[0]['edx_video_id'], constants.VIDEO_DICT_ANIMAL['edx_video_id'])
url = reverse('course-video-list', kwargs={'course_id': course1 + '/bad'})
response = self.client.get(url).data
self.assertEqual(len(response), 0)
# Tests for POST queries to database
def test_queries_for_only_video(self):
......@@ -459,7 +488,7 @@ class VideoListTest(APIAuthTestCase):
Tests number of queries for a Video with no Encoded Videos
"""
url = reverse('video-list')
with self.assertNumQueries(8):
with self.assertNumQueries(9):
self.client.post(url, constants.VIDEO_DICT_ZEBRA, format='json')
def test_queries_for_two_encoded_video(self):
......@@ -467,7 +496,7 @@ class VideoListTest(APIAuthTestCase):
Tests number of queries for a Video/EncodedVideo(2) pair
"""
url = reverse('video-list')
with self.assertNumQueries(20):
with self.assertNumQueries(21):
self.client.post(url, constants.COMPLETE_SET_FISH, format='json')
def test_queries_for_single_encoded_videos(self):
......@@ -475,7 +504,7 @@ class VideoListTest(APIAuthTestCase):
Tests number of queries for a Video/EncodedVideo(1) pair
"""
url = reverse('video-list')
with self.assertNumQueries(14):
with self.assertNumQueries(15):
self.client.post(url, constants.COMPLETE_SET_STAR, format='json')
......@@ -512,15 +541,15 @@ class VideoDetailTest(APIAuthTestCase):
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
response = self.client.post(url, constants.VIDEO_DICT_ZEBRA, format='json')
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
with self.assertNumQueries(6):
with self.assertNumQueries(7):
self.client.get("/edxval/video/").data
response = self.client.post(url, constants.COMPLETE_SET_FISH, format='json')
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
with self.assertNumQueries(11):
with self.assertNumQueries(12):
self.client.get("/edxval/video/").data
response = self.client.post(url, constants.COMPLETE_SET_STAR, format='json')
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
with self.assertNumQueries(14):
with self.assertNumQueries(15):
self.client.get("/edxval/video/").data
......
......@@ -28,4 +28,10 @@ urlpatterns = patterns(
views.get_subtitle,
name="subtitle-content"
),
url(
r'^edxval/course/(?P<course_id>[-\w/]+)$',
views.CourseVideoList.as_view(),
name="course-video-list"
),
)
......@@ -34,11 +34,21 @@ class VideoList(generics.ListCreateAPIView):
GETs or POST video objects
"""
permission_classes = (DjangoModelPermissions,)
queryset = Video.objects.all().prefetch_related("encoded_videos")
queryset = Video.objects.all().prefetch_related("encoded_videos", "courses")
lookup_field = "edx_video_id"
serializer_class = VideoSerializer
class CourseVideoList(generics.ListAPIView):
permission_classes = (DjangoModelPermissions,)
queryset = Video.objects.all().prefetch_related("encoded_videos")
lookup_field = "course_id"
serializer_class = VideoSerializer
def get_queryset(self):
return self.queryset.filter(courses__course_id=self.kwargs['course_id'])
class ProfileList(generics.ListCreateAPIView):
"""
GETs or POST video objects
......
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