Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
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-platform
Commits
ccf6c23d
Commit
ccf6c23d
authored
Apr 20, 2017
by
M. Rehan
Committed by
GitHub
Apr 20, 2017
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #14924 from edx/mrehan/add-hls-feature-flag
Add Configuration Models for HLS Playback Feature.
parents
cc330a8c
b2cfad16
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
334 additions
and
6 deletions
+334
-6
cms/envs/common.py
+3
-3
common/lib/xmodule/xmodule/video_module/video_module.py
+2
-1
lms/djangoapps/courseware/tests/test_video_mongo.py
+28
-2
lms/envs/common.py
+3
-0
openedx/core/djangoapps/video_config/__init__.py
+1
-0
openedx/core/djangoapps/video_config/admin.py
+27
-0
openedx/core/djangoapps/video_config/forms.py
+45
-0
openedx/core/djangoapps/video_config/migrations/0001_initial.py
+45
-0
openedx/core/djangoapps/video_config/migrations/__init__.py
+0
-0
openedx/core/djangoapps/video_config/models.py
+68
-0
openedx/core/djangoapps/video_config/tests/__init__.py
+0
-0
openedx/core/djangoapps/video_config/tests/test_models.py
+112
-0
No files found.
cms/envs/common.py
View file @
ccf6c23d
...
@@ -230,9 +230,6 @@ FEATURES = {
...
@@ -230,9 +230,6 @@ FEATURES = {
# Whether or not the dynamic EnrollmentTrackUserPartition should be registered.
# Whether or not the dynamic EnrollmentTrackUserPartition should be registered.
'ENABLE_ENROLLMENT_TRACK_USER_PARTITION'
:
False
,
'ENABLE_ENROLLMENT_TRACK_USER_PARTITION'
:
False
,
# Fetch `hls` profile from edx-val or not
'ENABLE_HLS_VIDEO_PROFILE'
:
False
,
}
}
ENABLE_JASMINE
=
False
ENABLE_JASMINE
=
False
...
@@ -937,6 +934,9 @@ INSTALLED_APPS = (
...
@@ -937,6 +934,9 @@ INSTALLED_APPS = (
# Self-paced course configuration
# Self-paced course configuration
'openedx.core.djangoapps.self_paced'
,
'openedx.core.djangoapps.self_paced'
,
# Video module configs (This will be moved to Video once it becomes an XBlock)
'openedx.core.djangoapps.video_config'
,
# django-oauth2-provider (deprecated)
# django-oauth2-provider (deprecated)
'provider'
,
'provider'
,
'provider.oauth2'
,
'provider.oauth2'
,
...
...
common/lib/xmodule/xmodule/video_module/video_module.py
View file @
ccf6c23d
...
@@ -24,6 +24,7 @@ from pkg_resources import resource_string
...
@@ -24,6 +24,7 @@ from pkg_resources import resource_string
from
django.conf
import
settings
from
django.conf
import
settings
from
openedx.core.lib.cache_utils
import
memoize_in_request_cache
from
openedx.core.lib.cache_utils
import
memoize_in_request_cache
from
openedx.core.djangoapps.video_config.models
import
HLSPlaybackEnabledFlag
from
xblock.core
import
XBlock
from
xblock.core
import
XBlock
from
xblock.fields
import
ScopeIds
from
xblock.fields
import
ScopeIds
from
xblock.runtime
import
KvsFieldData
from
xblock.runtime
import
KvsFieldData
...
@@ -219,7 +220,7 @@ class VideoModule(VideoFields, VideoTranscriptsMixin, VideoStudentViewHandlers,
...
@@ -219,7 +220,7 @@ class VideoModule(VideoFields, VideoTranscriptsMixin, VideoStudentViewHandlers,
try
:
try
:
val_profiles
=
[
"youtube"
,
"desktop_webm"
,
"desktop_mp4"
]
val_profiles
=
[
"youtube"
,
"desktop_webm"
,
"desktop_mp4"
]
if
settings
.
FEATURES
.
get
(
'ENABLE_HLS_VIDEO_PROFILE'
,
False
):
if
HLSPlaybackEnabledFlag
.
feature_enabled
(
self
.
course_id
):
val_profiles
.
append
(
'hls'
)
val_profiles
.
append
(
'hls'
)
# strip edx_video_id to prevent ValVideoNotFoundError error if unwanted spaces are there. TNL-5769
# strip edx_video_id to prevent ValVideoNotFoundError error if unwanted spaces are there. TNL-5769
...
...
lms/djangoapps/courseware/tests/test_video_mongo.py
View file @
ccf6c23d
...
@@ -177,6 +177,7 @@ class TestVideoNonYouTube(TestVideo):
...
@@ -177,6 +177,7 @@ class TestVideoNonYouTube(TestVideo):
@attr
(
shard
=
1
)
@attr
(
shard
=
1
)
@ddt.ddt
class
TestGetHtmlMethod
(
BaseTestXmodule
):
class
TestGetHtmlMethod
(
BaseTestXmodule
):
'''
'''
Make sure that `get_html` works correctly.
Make sure that `get_html` works correctly.
...
@@ -855,7 +856,33 @@ class TestGetHtmlMethod(BaseTestXmodule):
...
@@ -855,7 +856,33 @@ class TestGetHtmlMethod(BaseTestXmodule):
self
.
item_descriptor
.
xmodule_runtime
.
render_template
(
'video.html'
,
expected_context
)
self
.
item_descriptor
.
xmodule_runtime
.
render_template
(
'video.html'
,
expected_context
)
)
)
@patch.dict
(
'django.conf.settings.FEATURES'
,
{
'ENABLE_HLS_VIDEO_PROFILE'
:
True
})
@ddt.data
(
(
True
,
[
'youtube'
,
'desktop_webm'
,
'desktop_mp4'
,
'hls'
]),
(
False
,
[
'youtube'
,
'desktop_webm'
,
'desktop_mp4'
])
)
@ddt.unpack
def
test_get_html_on_toggling_hls_feature
(
self
,
hls_feature_enabled
,
expected_val_profiles
):
"""
Verify val profiles on toggling HLS Playback feature.
"""
with
patch
(
'xmodule.video_module.video_module.edxval_api.get_urls_for_profiles'
)
as
get_urls_for_profiles
:
get_urls_for_profiles
.
return_value
=
{
'desktop_webm'
:
'https://webm.com/dw.webm'
,
'hls'
:
'https://hls.com/hls.m3u8'
,
'youtube'
:
'https://yt.com/?v=v0TFmdO4ZP0'
,
'desktop_mp4'
:
'https://mp4.com/dm.mp4'
}
with
patch
(
'xmodule.video_module.video_module.HLSPlaybackEnabledFlag.feature_enabled'
)
as
feature_enabled
:
feature_enabled
.
return_value
=
hls_feature_enabled
video_xml
=
'<video display_name="Video" download_video="true" edx_video_id="12345-67890">[]</video>'
self
.
initialize_module
(
data
=
video_xml
)
self
.
item_descriptor
.
render
(
STUDENT_VIEW
)
get_urls_for_profiles
.
assert_called_with
(
self
.
item_descriptor
.
edx_video_id
,
expected_val_profiles
,
)
@patch
(
'xmodule.video_module.video_module.HLSPlaybackEnabledFlag.feature_enabled'
,
Mock
(
return_value
=
True
))
@patch
(
'xmodule.video_module.video_module.edxval_api.get_urls_for_profiles'
)
@patch
(
'xmodule.video_module.video_module.edxval_api.get_urls_for_profiles'
)
def
test_get_html_hls
(
self
,
get_urls_for_profiles
):
def
test_get_html_hls
(
self
,
get_urls_for_profiles
):
"""
"""
...
@@ -882,7 +909,6 @@ class TestGetHtmlMethod(BaseTestXmodule):
...
@@ -882,7 +909,6 @@ class TestGetHtmlMethod(BaseTestXmodule):
'"sources": ["https://webm.com/dw.webm", "https://mp4.com/dm.mp4", "https://hls.com/hls.m3u8"]'
,
context
'"sources": ["https://webm.com/dw.webm", "https://mp4.com/dm.mp4", "https://hls.com/hls.m3u8"]'
,
context
)
)
@patch.dict
(
'django.conf.settings.FEATURES'
,
{
'ENABLE_HLS_VIDEO_PROFILE'
:
True
})
def
test_get_html_hls_no_video_id
(
self
):
def
test_get_html_hls_no_video_id
(
self
):
"""
"""
Verify that `download_video_link` is set to None for HLS videos if no video id
Verify that `download_video_link` is set to None for HLS videos if no video id
...
...
lms/envs/common.py
View file @
ccf6c23d
...
@@ -2153,6 +2153,9 @@ INSTALLED_APPS = (
...
@@ -2153,6 +2153,9 @@ INSTALLED_APPS = (
# Verified Track Content Cohorting (Beta feature that will hopefully be removed)
# Verified Track Content Cohorting (Beta feature that will hopefully be removed)
'openedx.core.djangoapps.verified_track_content'
,
'openedx.core.djangoapps.verified_track_content'
,
# Video module configs (This will be moved to Video once it becomes an XBlock)
'openedx.core.djangoapps.video_config'
,
# Learner's dashboard
# Learner's dashboard
'learner_dashboard'
,
'learner_dashboard'
,
...
...
openedx/core/djangoapps/video_config/__init__.py
0 → 100644
View file @
ccf6c23d
# TODO Move this Application to video codebase when Video XModule becomes an XBlock. Reference: TNL-6867.
openedx/core/djangoapps/video_config/admin.py
0 → 100644
View file @
ccf6c23d
"""
Django admin dashboard configuration for Video XModule.
"""
from
django.contrib
import
admin
from
config_models.admin
import
ConfigurationModelAdmin
,
KeyedConfigurationModelAdmin
from
openedx.core.djangoapps.video_config.forms
import
CourseHLSPlaybackFlagAdminForm
from
openedx.core.djangoapps.video_config.models
import
CourseHLSPlaybackEnabledFlag
,
HLSPlaybackEnabledFlag
class
CourseHLSPlaybackEnabledFlagAdmin
(
KeyedConfigurationModelAdmin
):
"""
Admin of HLS Playback feature on course-by-course basis.
Allows searching by course id.
"""
form
=
CourseHLSPlaybackFlagAdminForm
search_fields
=
[
'course_id'
]
fieldsets
=
(
(
None
,
{
'fields'
:
(
'course_id'
,
'enabled'
),
'description'
:
'Enter a valid course id. If it is invalid, an error message will be displayed.'
}),
)
admin
.
site
.
register
(
HLSPlaybackEnabledFlag
,
ConfigurationModelAdmin
)
admin
.
site
.
register
(
CourseHLSPlaybackEnabledFlag
,
CourseHLSPlaybackEnabledFlagAdmin
)
openedx/core/djangoapps/video_config/forms.py
0 → 100644
View file @
ccf6c23d
"""
Defines a form for providing validation of HLS Playback course-specific configuration.
"""
import
logging
from
django
import
forms
from
openedx.core.djangoapps.video_config.models
import
CourseHLSPlaybackEnabledFlag
from
opaque_keys
import
InvalidKeyError
from
xmodule.modulestore.django
import
modulestore
from
opaque_keys.edx.locator
import
CourseLocator
log
=
logging
.
getLogger
(
__name__
)
class
CourseHLSPlaybackFlagAdminForm
(
forms
.
ModelForm
):
"""
Form for course-specific HLS Playback configuration.
"""
class
Meta
(
object
):
model
=
CourseHLSPlaybackEnabledFlag
fields
=
'__all__'
def
clean_course_id
(
self
):
"""
Validate the course id
"""
cleaned_id
=
self
.
cleaned_data
[
"course_id"
]
try
:
course_key
=
CourseLocator
.
from_string
(
cleaned_id
)
except
InvalidKeyError
:
msg
=
u'Course id invalid. Entered course id was: "{course_id}."'
.
format
(
course_id
=
cleaned_id
)
raise
forms
.
ValidationError
(
msg
)
if
not
modulestore
()
.
has_course
(
course_key
):
msg
=
u'Course not found. Entered course id was: "{course_key}". '
.
format
(
course_key
=
unicode
(
course_key
)
)
raise
forms
.
ValidationError
(
msg
)
return
course_key
openedx/core/djangoapps/video_config/migrations/0001_initial.py
0 → 100644
View file @
ccf6c23d
# -*- coding: utf-8 -*-
from
__future__
import
unicode_literals
from
django.db
import
migrations
,
models
from
django.conf
import
settings
import
django.db.models.deletion
import
openedx.core.djangoapps.xmodule_django.models
class
Migration
(
migrations
.
Migration
):
dependencies
=
[
migrations
.
swappable_dependency
(
settings
.
AUTH_USER_MODEL
),
]
operations
=
[
migrations
.
CreateModel
(
name
=
'CourseHLSPlaybackEnabledFlag'
,
fields
=
[
(
'id'
,
models
.
AutoField
(
verbose_name
=
'ID'
,
serialize
=
False
,
auto_created
=
True
,
primary_key
=
True
)),
(
'change_date'
,
models
.
DateTimeField
(
auto_now_add
=
True
,
verbose_name
=
'Change date'
)),
(
'enabled'
,
models
.
BooleanField
(
default
=
False
,
verbose_name
=
'Enabled'
)),
(
'course_id'
,
openedx
.
core
.
djangoapps
.
xmodule_django
.
models
.
CourseKeyField
(
max_length
=
255
,
db_index
=
True
)),
(
'changed_by'
,
models
.
ForeignKey
(
on_delete
=
django
.
db
.
models
.
deletion
.
PROTECT
,
editable
=
False
,
to
=
settings
.
AUTH_USER_MODEL
,
null
=
True
,
verbose_name
=
'Changed by'
)),
],
options
=
{
'ordering'
:
(
'-change_date'
,),
'abstract'
:
False
,
},
),
migrations
.
CreateModel
(
name
=
'HLSPlaybackEnabledFlag'
,
fields
=
[
(
'id'
,
models
.
AutoField
(
verbose_name
=
'ID'
,
serialize
=
False
,
auto_created
=
True
,
primary_key
=
True
)),
(
'change_date'
,
models
.
DateTimeField
(
auto_now_add
=
True
,
verbose_name
=
'Change date'
)),
(
'enabled'
,
models
.
BooleanField
(
default
=
False
,
verbose_name
=
'Enabled'
)),
(
'enabled_for_all_courses'
,
models
.
BooleanField
(
default
=
False
)),
(
'changed_by'
,
models
.
ForeignKey
(
on_delete
=
django
.
db
.
models
.
deletion
.
PROTECT
,
editable
=
False
,
to
=
settings
.
AUTH_USER_MODEL
,
null
=
True
,
verbose_name
=
'Changed by'
)),
],
options
=
{
'ordering'
:
(
'-change_date'
,),
'abstract'
:
False
,
},
),
]
openedx/core/djangoapps/video_config/migrations/__init__.py
0 → 100644
View file @
ccf6c23d
openedx/core/djangoapps/video_config/models.py
0 → 100644
View file @
ccf6c23d
"""
Configuration models for Video XModule
"""
from
config_models.models
import
ConfigurationModel
from
django.db.models
import
BooleanField
from
openedx.core.djangoapps.xmodule_django.models
import
CourseKeyField
class
HLSPlaybackEnabledFlag
(
ConfigurationModel
):
"""
Enables HLS Playback across the platform.
When this feature flag is set to true, individual courses
must also have HLS Playback enabled for this feature to
take effect.
"""
# this field overrides course-specific settings
enabled_for_all_courses
=
BooleanField
(
default
=
False
)
@classmethod
def
feature_enabled
(
cls
,
course_id
):
"""
Looks at the currently active configuration model to determine whether
the HLS Playback feature is available.
If the feature flag is not enabled, the feature is not available.
If the flag is enabled for all the courses, feature is available.
If the flag is enabled and the provided course_id is for an course
with HLS Playback enabled, the feature is available.
Arguments:
course_id (CourseKey): course id for whom feature will be checked.
"""
if
not
HLSPlaybackEnabledFlag
.
is_enabled
():
return
False
elif
not
HLSPlaybackEnabledFlag
.
current
()
.
enabled_for_all_courses
:
feature
=
(
CourseHLSPlaybackEnabledFlag
.
objects
.
filter
(
course_id
=
course_id
)
.
order_by
(
'-change_date'
)
.
first
())
return
feature
.
enabled
if
feature
else
False
return
True
def
__unicode__
(
self
):
current_model
=
HLSPlaybackEnabledFlag
.
current
()
return
u"HLSPlaybackEnabledFlag: enabled {is_enabled}"
.
format
(
is_enabled
=
current_model
.
is_enabled
()
)
class
CourseHLSPlaybackEnabledFlag
(
ConfigurationModel
):
"""
Enables HLS Playback for a specific course. Global feature must be
enabled for this to take effect.
"""
KEY_FIELDS
=
(
'course_id'
,)
course_id
=
CourseKeyField
(
max_length
=
255
,
db_index
=
True
)
def
__unicode__
(
self
):
not_en
=
"Not "
if
self
.
enabled
:
not_en
=
""
return
u"Course '{course_key}': HLS Playback {not_enabled}Enabled"
.
format
(
course_key
=
unicode
(
self
.
course_id
),
not_enabled
=
not_en
)
openedx/core/djangoapps/video_config/tests/__init__.py
0 → 100644
View file @
ccf6c23d
openedx/core/djangoapps/video_config/tests/test_models.py
0 → 100644
View file @
ccf6c23d
"""
Tests for the models that configures HLS Playback feature.
"""
import
ddt
import
itertools
from
contextlib
import
contextmanager
from
django.test
import
TestCase
from
opaque_keys.edx.locator
import
CourseLocator
from
openedx.core.djangoapps.video_config.models
import
CourseHLSPlaybackEnabledFlag
,
HLSPlaybackEnabledFlag
@contextmanager
def
hls_playback_feature_flags
(
global_flag
,
enabled_for_all_courses
=
False
,
course_id
=
None
,
enabled_for_course
=
False
):
"""
Yields HLS Playback Configuration records for unit tests
Arguments:
global_flag (bool): Specifies whether feature is enabled globally
enabled_for_all_courses (bool): Specifies whether feature is enabled for all courses
course_id (CourseLocator): Course locator for course specific configurations
enabled_for_course (bool): Specifies whether feature should be available for a course
"""
HLSPlaybackEnabledFlag
.
objects
.
create
(
enabled
=
global_flag
,
enabled_for_all_courses
=
enabled_for_all_courses
)
if
course_id
:
CourseHLSPlaybackEnabledFlag
.
objects
.
create
(
course_id
=
course_id
,
enabled
=
enabled_for_course
)
yield
@ddt.ddt
class
TestHLSPlaybackFlag
(
TestCase
):
"""
Tests the behavior of the flags for HLS Playback feature.
These are set via Django admin settings.
"""
def
setUp
(
self
):
super
(
TestHLSPlaybackFlag
,
self
)
.
setUp
()
self
.
course_id_1
=
CourseLocator
(
org
=
"edx"
,
course
=
"course"
,
run
=
"run"
)
self
.
course_id_2
=
CourseLocator
(
org
=
"edx"
,
course
=
"course2"
,
run
=
"run"
)
@ddt.data
(
*
itertools
.
product
(
(
True
,
False
),
(
True
,
False
),
(
True
,
False
),
)
)
@ddt.unpack
def
test_hls_playback_feature_flags
(
self
,
global_flag
,
enabled_for_all_courses
,
enabled_for_course_1
):
"""
Tests that the feature flags works correctly on tweaking global flags in combination
with course-specific flags.
"""
with
hls_playback_feature_flags
(
global_flag
=
global_flag
,
enabled_for_all_courses
=
enabled_for_all_courses
,
course_id
=
self
.
course_id_1
,
enabled_for_course
=
enabled_for_course_1
):
self
.
assertEqual
(
HLSPlaybackEnabledFlag
.
feature_enabled
(
self
.
course_id_1
),
global_flag
and
(
enabled_for_all_courses
or
enabled_for_course_1
)
)
self
.
assertEqual
(
HLSPlaybackEnabledFlag
.
feature_enabled
(
self
.
course_id_2
),
global_flag
and
enabled_for_all_courses
)
def
test_enable_disable_course_flag
(
self
):
"""
Ensures that the flag, once enabled for a course, can also be disabled.
"""
with
hls_playback_feature_flags
(
global_flag
=
True
,
enabled_for_all_courses
=
False
,
course_id
=
self
.
course_id_1
,
enabled_for_course
=
True
):
self
.
assertTrue
(
HLSPlaybackEnabledFlag
.
feature_enabled
(
self
.
course_id_1
))
with
hls_playback_feature_flags
(
global_flag
=
True
,
enabled_for_all_courses
=
False
,
course_id
=
self
.
course_id_1
,
enabled_for_course
=
False
):
self
.
assertFalse
(
HLSPlaybackEnabledFlag
.
feature_enabled
(
self
.
course_id_1
))
def
test_enable_disable_globally
(
self
):
"""
Ensures that the flag, once enabled globally, can also be disabled.
"""
with
hls_playback_feature_flags
(
global_flag
=
True
,
enabled_for_all_courses
=
True
,
):
self
.
assertTrue
(
HLSPlaybackEnabledFlag
.
feature_enabled
(
self
.
course_id_1
))
self
.
assertTrue
(
HLSPlaybackEnabledFlag
.
feature_enabled
(
self
.
course_id_2
))
with
hls_playback_feature_flags
(
global_flag
=
True
,
enabled_for_all_courses
=
False
,
):
self
.
assertFalse
(
HLSPlaybackEnabledFlag
.
feature_enabled
(
self
.
course_id_1
))
self
.
assertFalse
(
HLSPlaybackEnabledFlag
.
feature_enabled
(
self
.
course_id_2
))
with
hls_playback_feature_flags
(
global_flag
=
False
,
):
self
.
assertFalse
(
HLSPlaybackEnabledFlag
.
feature_enabled
(
self
.
course_id_1
))
self
.
assertFalse
(
HLSPlaybackEnabledFlag
.
feature_enabled
(
self
.
course_id_2
))
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