Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-val
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-val
Commits
d3000dd2
Commit
d3000dd2
authored
Sep 04, 2014
by
Dave St.Germain
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Expose course_id to the API
parent
01f3b9c1
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
109 additions
and
17 deletions
+109
-17
edxval/admin.py
+2
-1
edxval/api.py
+8
-1
edxval/models.py
+7
-1
edxval/serializers.py
+22
-1
edxval/tests/test_api.py
+18
-6
edxval/tests/test_views.py
+35
-6
edxval/urls.py
+6
-0
edxval/views.py
+11
-1
No files found.
edxval/admin.py
View file @
d3000dd2
...
...
@@ -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
)
edxval/api.py
View file @
d3000dd2
...
...
@@ -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
)
edxval/models.py
View file @
d3000dd2
...
...
@@ -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
):
"""
...
...
edxval/serializers.py
View file @
d3000dd2
...
...
@@ -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
)):
...
...
edxval/tests/test_api.py
View file @
d3000dd2
...
...
@@ -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"
))
edxval/tests/test_views.py
View file @
d3000dd2
...
...
@@ -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
(
2
0
):
with
self
.
assertNumQueries
(
2
1
):
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
(
1
4
):
with
self
.
assertNumQueries
(
1
5
):
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
(
1
1
):
with
self
.
assertNumQueries
(
1
2
):
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
(
1
4
):
with
self
.
assertNumQueries
(
1
5
):
self
.
client
.
get
(
"/edxval/video/"
)
.
data
...
...
edxval/urls.py
View file @
d3000dd2
...
...
@@ -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"
),
)
edxval/views.py
View file @
d3000dd2
...
...
@@ -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
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment