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
64aa7637
Commit
64aa7637
authored
Mar 23, 2015
by
Christopher Lee
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #39 from edx/clee/MA-77_auto_course_reruns
Clee/ma 77 auto course reruns
parents
f4b3441f
6bba9e86
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
397 additions
and
31 deletions
+397
-31
.coveragerc
+2
-2
edxval/api.py
+93
-18
edxval/tests/constants.py
+36
-4
edxval/tests/test_api.py
+266
-7
No files found.
.coveragerc
View file @
64aa7637
...
...
@@ -6,4 +6,5 @@ include = edxval/*
omit =
**/__init__.py
**/tests/*
**/settings.py
\ No newline at end of file
**/settings.py
**/migrations*
edxval/api.py
View file @
64aa7637
...
...
@@ -6,7 +6,7 @@ The internal API for VAL. This is not yet stable
from
enum
import
Enum
import
logging
from
edxval.models
import
Video
,
EncodedVideo
from
edxval.models
import
Video
,
EncodedVideo
,
CourseVideo
from
edxval.serializers
import
VideoSerializer
,
ProfileSerializer
logger
=
logging
.
getLogger
(
__name__
)
# pylint: disable=C0103
...
...
@@ -93,6 +93,7 @@ def create_video(video_data):
extension: 3 letter extension of video
width: horizontal pixel resolution
height: vertical pixel resolution
courses: Courses associated with this video
}
"""
serializer
=
VideoSerializer
(
data
=
video_data
)
...
...
@@ -119,7 +120,7 @@ def create_profile(profile_data):
}
Returns:
new_object.id
(int): id of the newly created object
(int): id of the newly created object
Raises:
ValCannotCreateError: Raised if the serializer throws an error
...
...
@@ -141,7 +142,7 @@ def get_video_info(edx_video_id, location=None): # pylint: disable=W0613
edx_video_id (str): id for video content.
Returns:
result
(dict): Deserialized Video Object with related field EncodedVideo
(dict): Deserialized Video Object with related field EncodedVideo
Returns all the Video object fields, and it's related EncodedVideo
objects in a list.
{
...
...
@@ -180,7 +181,7 @@ def get_video_info(edx_video_id, location=None): # pylint: disable=W0613
'client_video_id': u'The example video',
'encoded_videos': [
{
'url': u'http://www.
meowmix
.com',
'url': u'http://www.
example
.com',
'file_size': 25556,
'bitrate': 9600,
'profile': u'mobile'
...
...
@@ -212,7 +213,7 @@ def get_urls_for_profiles(edx_video_id, profiles):
profiles (list): list of profiles we want to search for
Returns:
profiles_to_urls
(dict): A dict containing the profile to url pair
(dict): A dict containing the profile to url pair
"""
profiles_to_urls
=
{
profile
:
None
for
profile
in
profiles
}
try
:
...
...
@@ -236,7 +237,7 @@ def get_url_for_profile(edx_video_id, profile):
profile (str): a string of the profile we are searching
Returns:
A string with the url
(str):
A string with the url
"""
return
get_urls_for_profiles
(
edx_video_id
,
[
profile
])[
profile
]
...
...
@@ -277,17 +278,63 @@ def get_videos_for_ids(
return
(
VideoSerializer
(
video
)
.
data
for
video
in
videos
)
def
get_video_info_for_course_and_profile
(
course_id
,
profile_name
):
def
get_video_info_for_course_and_profile
s
(
course_id
,
profiles
):
"""
Returns a dict
mapping profiles to URL
s.
Returns a dict
of edx_video_ids with a dict of requested profile
s.
If the profiles or video is not found, urls will be blank.
Args:
course_id (str): id of the course
profiles (list): list of profile_names
Returns:
(dict): Returns all the profiles attached to a specific
edx_video_id
{
edx_video_id: {
'duration': length of the video in seconds,
'profiles': {
profile_name: {
'url': url of the encoding
'file_size': size of the file in bytes
},
}
},
}
Example:
Given two videos with two profiles each in course_id 'test_course':
{
u'edx_video_id_1': {
u'duration: 1111,
u'profiles': {
u'mobile': {
'url': u'http: //www.example.com/meow',
'file_size': 2222
},
u'desktop': {
'url': u'http: //www.example.com/woof',
'file_size': 4444
}
}
},
u'edx_video_id_2': {
u'duration: 2222,
u'profiles': {
u'mobile': {
'url': u'http: //www.example.com/roar',
'file_size': 6666
},
u'desktop': {
'url': u'http: //www.example.com/bzzz',
'file_size': 8888
}
}
}
}
"""
# In case someone passes in a key (VAL doesn't really understand opaque keys)
course_id
=
unicode
(
course_id
)
try
:
encoded_videos
=
EncodedVideo
.
objects
.
filter
(
profile__profile_name
=
profile_name
,
profile__profile_name
__in
=
profiles
,
video__courses__course_id
=
course_id
)
.
select_related
()
except
Exception
:
...
...
@@ -296,11 +343,39 @@ def get_video_info_for_course_and_profile(course_id, profile_name):
raise
ValInternalError
(
error_message
)
# DRF serializers were causing extra queries for some reason...
return
{
enc_vid
.
video
.
edx_video_id
:
{
"url"
:
enc_vid
.
url
,
"file_size"
:
enc_vid
.
file_size
,
"duration"
:
enc_vid
.
video
.
duration
,
}
for
enc_vid
in
encoded_videos
}
return_dict
=
{}
for
enc_vid
in
encoded_videos
:
# Add duration to edx_video_id
return_dict
.
setdefault
(
enc_vid
.
video
.
edx_video_id
,
{})
.
update
(
{
"duration"
:
enc_vid
.
video
.
duration
,
}
)
# Add profile information to edx_video_id's profiles
return_dict
[
enc_vid
.
video
.
edx_video_id
]
.
setdefault
(
"profiles"
,
{})
.
update
(
{
enc_vid
.
profile
.
profile_name
:
{
"url"
:
enc_vid
.
url
,
"file_size"
:
enc_vid
.
file_size
,
}}
)
return
return_dict
def
copy_course_videos
(
source_course_id
,
destination_course_id
):
"""
Adds the destination_course_id to the videos taken from the source_course_id
Args:
source_course_id: The original course_id
destination_course_id: The new course_id where the videos will be copied
"""
if
source_course_id
==
destination_course_id
:
return
videos
=
Video
.
objects
.
filter
(
courses__course_id
=
unicode
(
source_course_id
))
for
video
in
videos
:
CourseVideo
.
objects
.
get_or_create
(
video
=
video
,
course_id
=
destination_course_id
)
edxval/tests/constants.py
View file @
64aa7637
...
...
@@ -30,13 +30,33 @@ Encoded_videos for test_api, does not have profile.
"""
ENCODED_VIDEO_DICT_MOBILE
=
dict
(
url
=
"http://www.meowmix.com"
,
file_size
=
4545
,
bitrate
=
6767
,
file_size
=
11
,
bitrate
=
22
,
)
ENCODED_VIDEO_DICT_DESKTOP
=
dict
(
url
=
"http://www.meowmagic.com"
,
file_size
=
1212
,
bitrate
=
2323
,
file_size
=
33
,
bitrate
=
44
,
)
ENCODED_VIDEO_DICT_MOBILE2
=
dict
(
url
=
"http://www.woof.com"
,
file_size
=
55
,
bitrate
=
66
,
)
ENCODED_VIDEO_DICT_DESKTOP2
=
dict
(
url
=
"http://www.bark.com"
,
file_size
=
77
,
bitrate
=
88
,
)
ENCODED_VIDEO_DICT_MOBILE3
=
dict
(
url
=
"http://www.ssss.com"
,
file_size
=
1111
,
bitrate
=
2222
,
)
ENCODED_VIDEO_DICT_DESKTOP3
=
dict
(
url
=
"http://www.hiss.com"
,
file_size
=
3333
,
bitrate
=
4444
,
)
"""
Validators
...
...
@@ -322,6 +342,18 @@ COMPLETE_SET_EXTRA_VIDEO_FIELD = dict(
"""
Other
"""
VIDEO_DICT_TREE
=
dict
(
client_video_id
=
"Trees4lyfe"
,
duration
=
532.00
,
edx_video_id
=
"tree-hugger"
,
status
=
"test"
,
)
VIDEO_DICT_PLANT
=
dict
(
client_video_id
=
"PlantzRule"
,
duration
=
876.00
,
edx_video_id
=
"fernmaster"
,
status
=
"test"
,
)
VIDEO_DICT_ZEBRA
=
dict
(
client_video_id
=
"Zesty Zebra"
,
duration
=
111.00
,
...
...
edxval/tests/test_api.py
View file @
64aa7637
...
...
@@ -14,10 +14,15 @@ from ddt import ddt, data
from
edxval.models
import
Profile
,
Video
,
EncodedVideo
,
CourseVideo
from
edxval
import
api
as
api
from
edxval.api
import
SortDirection
,
ValCannotCreateError
,
VideoSortField
from
edxval.api
import
(
SortDirection
,
ValCannotCreateError
,
VideoSortField
,
)
from
edxval.serializers
import
VideoSerializer
from
edxval.tests
import
constants
,
APIAuthTestCase
@ddt
class
CreateVideoTest
(
TestCase
):
"""
...
...
@@ -125,16 +130,12 @@ class GetVideoInfoTest(TestCase):
Profile
.
objects
.
create
(
**
constants
.
PROFILE_DICT_DESKTOP
)
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"
)
),
video
=
video
,
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_FISH
.
get
(
"edx_video_id"
)
),
video
=
video
,
profile
=
Profile
.
objects
.
get
(
profile_name
=
"desktop"
),
**
constants
.
ENCODED_VIDEO_DICT_DESKTOP
)
...
...
@@ -269,6 +270,210 @@ class GetUrlsForProfileTest(TestCase):
self
.
assertEqual
(
url
,
u'http://www.meowmix.com'
)
class
GetVideoForCourseProfiles
(
TestCase
):
"""Tests get_video_info_for_course_and_profiles in api.py"""
def
setUp
(
self
):
"""
Creates two courses for testing
Creates two videos with 2 encoded videos for the first course, and then
2 videos with 1 encoded video for the second course.
"""
mobile_profile
=
Profile
.
objects
.
create
(
**
constants
.
PROFILE_DICT_MOBILE
)
desktop_profile
=
Profile
.
objects
.
create
(
**
constants
.
PROFILE_DICT_DESKTOP
)
self
.
course_id
=
'test-course'
# 1st video
video
=
Video
.
objects
.
create
(
**
constants
.
VIDEO_DICT_FISH
)
EncodedVideo
.
objects
.
create
(
video
=
video
,
profile
=
mobile_profile
,
**
constants
.
ENCODED_VIDEO_DICT_MOBILE
)
EncodedVideo
.
objects
.
create
(
video
=
video
,
profile
=
desktop_profile
,
**
constants
.
ENCODED_VIDEO_DICT_DESKTOP
)
CourseVideo
.
objects
.
create
(
video
=
video
,
course_id
=
self
.
course_id
)
# 2nd video
video
=
Video
.
objects
.
create
(
**
constants
.
VIDEO_DICT_STAR
)
EncodedVideo
.
objects
.
create
(
video
=
video
,
profile
=
mobile_profile
,
**
constants
.
ENCODED_VIDEO_DICT_MOBILE2
)
EncodedVideo
.
objects
.
create
(
video
=
video
,
profile
=
desktop_profile
,
**
constants
.
ENCODED_VIDEO_DICT_DESKTOP2
)
CourseVideo
.
objects
.
create
(
video
=
video
,
course_id
=
self
.
course_id
)
self
.
course_id2
=
"test-course2"
# 3rd video different course
video
=
Video
.
objects
.
create
(
**
constants
.
VIDEO_DICT_TREE
)
EncodedVideo
.
objects
.
create
(
video
=
video
,
profile
=
mobile_profile
,
**
constants
.
ENCODED_VIDEO_DICT_MOBILE3
)
CourseVideo
.
objects
.
create
(
video
=
video
,
course_id
=
self
.
course_id2
)
# 4th video different course
video
=
Video
.
objects
.
create
(
**
constants
.
VIDEO_DICT_PLANT
)
EncodedVideo
.
objects
.
create
(
video
=
video
,
profile
=
desktop_profile
,
**
constants
.
ENCODED_VIDEO_DICT_DESKTOP3
)
CourseVideo
.
objects
.
create
(
video
=
video
,
course_id
=
self
.
course_id2
)
def
_create_video_dict
(
self
,
video
,
encoding_dict
):
"""
Creates a video dict object from given constants
"""
return
{
video
[
'edx_video_id'
]:
{
"duration"
:
video
[
'duration'
],
'profiles'
:
{
profile_name
:
{
"url"
:
encoding
[
"url"
],
"file_size"
:
encoding
[
"file_size"
],
}
for
(
profile_name
,
encoding
)
in
encoding_dict
.
iteritems
()
}
}
}
def
test_get_video_for_course_profiles_success_one_profile
(
self
):
"""
Tests get_video_info_for_course_and_profiles for one profile
"""
videos
=
api
.
get_video_info_for_course_and_profiles
(
self
.
course_id
,
[
'mobile'
]
)
expected_dict
=
{}
expected_dict
.
update
(
self
.
_create_video_dict
(
constants
.
VIDEO_DICT_FISH
,
{
constants
.
PROFILE_DICT_MOBILE
[
"profile_name"
]:
constants
.
ENCODED_VIDEO_DICT_MOBILE
}
))
expected_dict
.
update
(
self
.
_create_video_dict
(
constants
.
VIDEO_DICT_STAR
,
{
constants
.
PROFILE_DICT_MOBILE
[
"profile_name"
]:
constants
.
ENCODED_VIDEO_DICT_MOBILE2
}))
self
.
assertEqual
(
videos
,
expected_dict
)
def
test_get_video_for_course_profiles_success_two_profiles
(
self
):
"""
Tests get_video_info_for_course_and_profiles for two profile
"""
videos
=
api
.
get_video_info_for_course_and_profiles
(
'test-course'
,
[
'mobile'
,
'desktop'
])
expected_dict
=
{}
expected_dict
.
update
(
self
.
_create_video_dict
(
constants
.
VIDEO_DICT_FISH
,
{
constants
.
PROFILE_DICT_MOBILE
[
"profile_name"
]:
constants
.
ENCODED_VIDEO_DICT_MOBILE
,
constants
.
PROFILE_DICT_DESKTOP
[
"profile_name"
]:
constants
.
ENCODED_VIDEO_DICT_DESKTOP
,
}
))
expected_dict
.
update
(
self
.
_create_video_dict
(
constants
.
VIDEO_DICT_STAR
,
{
constants
.
PROFILE_DICT_MOBILE
[
"profile_name"
]:
constants
.
ENCODED_VIDEO_DICT_MOBILE2
,
constants
.
PROFILE_DICT_DESKTOP
[
"profile_name"
]:
constants
.
ENCODED_VIDEO_DICT_DESKTOP2
,
}
))
self
.
assertEqual
(
videos
,
expected_dict
)
def
test_get_video_for_course_profiles_no_profile
(
self
):
"""Tests get_video_info_for_course_and_profiles with no such profile"""
videos
=
api
.
get_video_info_for_course_and_profiles
(
'test-course'
,
[
'no_profile'
])
self
.
assertEqual
(
len
(
videos
),
0
)
videos
=
api
.
get_video_info_for_course_and_profiles
(
'test-course'
,
[])
self
.
assertEqual
(
len
(
videos
),
0
)
videos
=
api
.
get_video_info_for_course_and_profiles
(
'test-course'
,
[
'mobile'
,
'no_profile'
])
expected_dict
=
{}
expected_dict
.
update
(
self
.
_create_video_dict
(
constants
.
VIDEO_DICT_FISH
,
{
constants
.
PROFILE_DICT_MOBILE
[
"profile_name"
]:
constants
.
ENCODED_VIDEO_DICT_MOBILE
}
))
expected_dict
.
update
(
self
.
_create_video_dict
(
constants
.
VIDEO_DICT_STAR
,
{
constants
.
PROFILE_DICT_MOBILE
[
"profile_name"
]:
constants
.
ENCODED_VIDEO_DICT_MOBILE2
}
))
self
.
assertEqual
(
videos
,
expected_dict
)
def
test_get_video_for_course_profiles_video_with_one_profile
(
self
):
"""
Tests get_video_info_for_course_and_profiles with one of two profiles
"""
videos
=
api
.
get_video_info_for_course_and_profiles
(
'test-course2'
,
[
'mobile'
])
expected_dict
=
{}
expected_dict
.
update
(
self
.
_create_video_dict
(
constants
.
VIDEO_DICT_TREE
,
{
constants
.
PROFILE_DICT_MOBILE
[
"profile_name"
]:
constants
.
ENCODED_VIDEO_DICT_MOBILE3
}
))
self
.
assertEqual
(
videos
,
expected_dict
)
videos
=
api
.
get_video_info_for_course_and_profiles
(
'test-course2'
,
[
'desktop'
])
expected_dict
=
{}
expected_dict
.
update
(
self
.
_create_video_dict
(
constants
.
VIDEO_DICT_PLANT
,
{
constants
.
PROFILE_DICT_DESKTOP
[
"profile_name"
]:
constants
.
ENCODED_VIDEO_DICT_DESKTOP3
}
))
self
.
assertEqual
(
videos
,
expected_dict
)
def
test_get_video_for_course_profiles_repeated_profile
(
self
):
"""
Tests get_video_info_for_course_and_profiles with repeated profile
"""
videos
=
api
.
get_video_info_for_course_and_profiles
(
'test-course'
,
[
'mobile'
,
'mobile'
])
expected_dict
=
{}
expected_dict
.
update
(
self
.
_create_video_dict
(
constants
.
VIDEO_DICT_FISH
,
{
constants
.
PROFILE_DICT_MOBILE
[
"profile_name"
]:
constants
.
ENCODED_VIDEO_DICT_MOBILE
,
}
))
expected_dict
.
update
(
self
.
_create_video_dict
(
constants
.
VIDEO_DICT_STAR
,
{
constants
.
PROFILE_DICT_MOBILE
[
"profile_name"
]:
constants
.
ENCODED_VIDEO_DICT_MOBILE2
}
))
self
.
assertEqual
(
videos
,
expected_dict
)
class
GetVideosForIds
(
TestCase
):
"""
Tests the get_videos_for_ids function in api.py
...
...
@@ -436,3 +641,57 @@ class GetVideoInfoTestWithHttpCalls(APIAuthTestCase):
with
self
.
assertNumQueries
(
4
):
api
.
get_video_info
(
constants
.
VIDEO_DICT_ZEBRA
.
get
(
"edx_video_id"
))
class
TestCopyCourse
(
TestCase
):
"""Tests copy_course_videos in api.py"""
def
setUp
(
self
):
"""
Creates a course with 2 videos and a course with 1 video
"""
self
.
course_id
=
'test-course'
# 1st video
video
=
Video
.
objects
.
create
(
**
constants
.
VIDEO_DICT_FISH
)
CourseVideo
.
objects
.
create
(
video
=
video
,
course_id
=
self
.
course_id
)
# 2nd video
video
=
Video
.
objects
.
create
(
**
constants
.
VIDEO_DICT_STAR
)
CourseVideo
.
objects
.
create
(
video
=
video
,
course_id
=
self
.
course_id
)
self
.
course_id2
=
"test-course2"
# 3rd video different course
video
=
Video
.
objects
.
create
(
**
constants
.
VIDEO_DICT_TREE
)
CourseVideo
.
objects
.
create
(
video
=
video
,
course_id
=
self
.
course_id2
)
def
test_successful_copy
(
self
):
"""Tests a successful copy course"""
api
.
copy_course_videos
(
'test-course'
,
'course-copy1'
)
original_videos
=
Video
.
objects
.
filter
(
courses__course_id
=
'test-course'
)
copied_videos
=
Video
.
objects
.
filter
(
courses__course_id
=
'course-copy1'
)
other_course
=
Video
.
objects
.
filter
(
courses__course_id
=
'test-course2'
)
self
.
assertEqual
(
len
(
original_videos
),
2
)
self
.
assertEqual
(
{
original_video
.
edx_video_id
for
original_video
in
original_videos
},
{
constants
.
VIDEO_DICT_FISH
[
"edx_video_id"
],
constants
.
VIDEO_DICT_STAR
[
"edx_video_id"
]}
)
self
.
assertTrue
(
set
(
original_videos
)
==
set
(
copied_videos
))
def
test_same_course_ids
(
self
):
"""
Tests when the destination course id name is the same as the source
"""
original_videos
=
Video
.
objects
.
filter
(
courses__course_id
=
'test-course'
)
api
.
copy_course_videos
(
'test-course'
,
'test-course'
)
copied_videos
=
Video
.
objects
.
filter
(
courses__course_id
=
'test-course'
)
self
.
assertEqual
(
len
(
original_videos
),
2
)
self
.
assertTrue
(
set
(
original_videos
)
==
set
(
copied_videos
))
def
test_existing_destination_course_id
(
self
):
"""Test when the destination course id already exists"""
api
.
copy_course_videos
(
'test-course'
,
'test-course2'
)
original_videos
=
Video
.
objects
.
filter
(
courses__course_id
=
'test-course'
)
copied_videos
=
Video
.
objects
.
filter
(
courses__course_id
=
'test-course2'
)
self
.
assertEqual
(
len
(
original_videos
),
2
)
self
.
assertLessEqual
(
set
(
original_videos
),
set
(
copied_videos
))
self
.
assertEqual
(
len
(
copied_videos
),
3
)
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