Merge pull request #6575 from edx/clrux/xuetangx-branding-update
Video player branding addition for XuetangX
This commit is contained in:
@@ -312,3 +312,7 @@ VIDEO_UPLOAD_PIPELINE = ENV_TOKENS.get('VIDEO_UPLOAD_PIPELINE', VIDEO_UPLOAD_PIP
|
||||
#date format the api will be formatting the datetime values
|
||||
API_DATE_FORMAT = '%Y-%m-%d'
|
||||
API_DATE_FORMAT = ENV_TOKENS.get('API_DATE_FORMAT', API_DATE_FORMAT)
|
||||
|
||||
# Video Caching. Pairing country codes with CDN URLs.
|
||||
# Example: {'CN': 'http://api.xuetangx.com/edx/video?s3_url='}
|
||||
VIDEO_CDN_URL = ENV_TOKENS.get('VIDEO_CDN_URL', {})
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
This config file runs the simplest dev environment using sqlite, and db-based
|
||||
sessions. Assumes structure:
|
||||
@@ -249,3 +250,7 @@ FEATURES['MILESTONES_APP'] = True
|
||||
# ENTRANCE EXAMS
|
||||
FEATURES['ENTRANCE_EXAMS'] = True
|
||||
ENTRANCE_EXAM_MIN_SCORE_PCT = 50
|
||||
|
||||
VIDEO_CDN_URL = {
|
||||
'CN': 'http://api.xuetangx.com/edx/video?s3_url='
|
||||
}
|
||||
|
||||
@@ -72,6 +72,31 @@ div.video {
|
||||
border-radius: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
.branding {
|
||||
display: inline-block;
|
||||
float: right;
|
||||
margin: 15px 0 0 10px;
|
||||
vertical-align: top;
|
||||
|
||||
.host-tag {
|
||||
@include margin-right($baseline/2);
|
||||
position: absolute;
|
||||
left: -9999em;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
font-size: 70%;
|
||||
color: #777;
|
||||
}
|
||||
|
||||
.brand-logo {
|
||||
display: inline-block;
|
||||
max-width: 100%;
|
||||
max-height: ($baseline*2);
|
||||
padding: ($baseline/4) 0;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
article.video-wrapper {
|
||||
@@ -741,7 +766,7 @@ div.video {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,14 +2,12 @@
|
||||
# pylint: disable=abstract-method
|
||||
"""Video is ungraded Xmodule for support video content.
|
||||
It's new improved video module, which support additional feature:
|
||||
|
||||
- Can play non-YouTube video sources via in-browser HTML5 video player.
|
||||
- YouTube defaults to HTML5 mode from the start.
|
||||
- Speed changes in both YouTube and non-YouTube videos happen via
|
||||
in-browser HTML5 video method (when in HTML5 mode).
|
||||
- Navigational subtitles can be disabled altogether via an attribute
|
||||
in XML.
|
||||
|
||||
Examples of html5 videos for manual testing:
|
||||
https://s3.amazonaws.com/edx-course-videos/edx-intro/edX-FA12-cware-1_100.mp4
|
||||
https://s3.amazonaws.com/edx-course-videos/edx-intro/edX-FA12-cware-1_100.webm
|
||||
@@ -73,6 +71,11 @@ try:
|
||||
except ImportError:
|
||||
edxval_api = None
|
||||
|
||||
try:
|
||||
from branding.models import BrandingInfoConfig
|
||||
except ImportError:
|
||||
BrandingInfoConfig = None
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
_ = lambda text: text
|
||||
|
||||
@@ -80,7 +83,6 @@ _ = lambda text: text
|
||||
class VideoModule(VideoFields, VideoTranscriptsMixin, VideoStudentViewHandlers, XModule):
|
||||
"""
|
||||
XML source example:
|
||||
|
||||
<video show_captions="true"
|
||||
youtube="0.75:jNCf2gIqpeE,1.0:ZwkTiUPN0mg,1.25:rsq9auxASqI,1.50:kMyNdzVHHgg"
|
||||
url_name="lecture_21_3" display_name="S19V3: Vacancies"
|
||||
@@ -129,12 +131,9 @@ class VideoModule(VideoFields, VideoTranscriptsMixin, VideoStudentViewHandlers,
|
||||
|
||||
def get_transcripts_for_student(self):
|
||||
"""Return transcript information necessary for rendering the XModule student view.
|
||||
|
||||
This is more or less a direct extraction from `get_html`.
|
||||
|
||||
Returns:
|
||||
Tuple of (track_url, transcript_language, sorted_languages)
|
||||
|
||||
track_url -> subtitle download url
|
||||
transcript_language -> default transcript language
|
||||
sorted_languages -> dictionary of available transcript languages
|
||||
@@ -175,6 +174,7 @@ class VideoModule(VideoFields, VideoTranscriptsMixin, VideoStudentViewHandlers,
|
||||
sources = filter(None, self.html5_sources)
|
||||
|
||||
download_video_link = None
|
||||
branding_info = None
|
||||
youtube_streams = ""
|
||||
|
||||
# If we have an edx_video_id, we prefer its values over what we store
|
||||
@@ -213,8 +213,9 @@ class VideoModule(VideoFields, VideoTranscriptsMixin, VideoStudentViewHandlers,
|
||||
# Video caching is disabled for Studio. User_location is always None in Studio.
|
||||
# CountryMiddleware disabled for Studio.
|
||||
cdn_url = getattr(settings, 'VIDEO_CDN_URL', {}).get(self.system.user_location)
|
||||
|
||||
if getattr(self, 'video_speed_optimizations', True) and cdn_url:
|
||||
branding_info = BrandingInfoConfig.get_config().get(self.system.user_location)
|
||||
|
||||
for index, source_url in enumerate(sources):
|
||||
new_url = get_video_from_cdn(cdn_url, source_url)
|
||||
if new_url:
|
||||
@@ -233,6 +234,7 @@ class VideoModule(VideoFields, VideoTranscriptsMixin, VideoStudentViewHandlers,
|
||||
return self.system.render_template('video.html', {
|
||||
'ajax_url': self.system.ajax_url + '/save_user_state',
|
||||
'autoplay': settings.FEATURES.get('AUTOPLAY_VIDEOS', False),
|
||||
'branding_info': branding_info,
|
||||
# This won't work when we move to data that
|
||||
# isn't on the filesystem
|
||||
'data_dir': getattr(self, 'data_dir', None),
|
||||
@@ -286,7 +288,6 @@ class VideoDescriptor(VideoFields, VideoTranscriptsMixin, VideoStudioViewHandler
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""
|
||||
Mostly handles backward compatibility issues.
|
||||
|
||||
`source` is deprecated field.
|
||||
a) If `source` exists and `source` is not `html5_sources`: show `source`
|
||||
field on front-end as not-editable but clearable. Dropdown is a new
|
||||
@@ -331,21 +332,16 @@ class VideoDescriptor(VideoFields, VideoTranscriptsMixin, VideoStudioViewHandler
|
||||
def editor_saved(self, user, old_metadata, old_content):
|
||||
"""
|
||||
Used to update video values during `self`:save method from CMS.
|
||||
|
||||
old_metadata: dict, values of fields of `self` with scope=settings which were explicitly set by user.
|
||||
old_content, same as `old_metadata` but for scope=content.
|
||||
|
||||
Due to nature of code flow in item.py::_save_item, before current function is called,
|
||||
fields of `self` instance have been already updated, but not yet saved.
|
||||
|
||||
To obtain values, which were changed by user input,
|
||||
one should compare own_metadata(self) and old_medatada.
|
||||
|
||||
Video player has two tabs, and due to nature of sync between tabs,
|
||||
metadata from Basic tab is always sent when video player is edited and saved first time, for example:
|
||||
{'youtube_id_1_0': u'OEoXaMPEzfM', 'display_name': u'Video', 'sub': u'OEoXaMPEzfM', 'html5_sources': []},
|
||||
that's why these fields will always present in old_metadata after first save. This should be fixed.
|
||||
|
||||
At consequent save requests html5_sources are always sent too, disregard of their change by user.
|
||||
That means that html5_sources are always in list of fields that were changed (`metadata` param in save_item).
|
||||
This should be fixed too.
|
||||
@@ -390,7 +386,6 @@ class VideoDescriptor(VideoFields, VideoTranscriptsMixin, VideoStudioViewHandler
|
||||
"""
|
||||
Creates an instance of this descriptor from the supplied xml_data.
|
||||
This may be overridden by subclasses
|
||||
|
||||
xml_data: A string of xml that will be translated into data and children for
|
||||
this module
|
||||
system: A DescriptorSystem for interacting with external resources
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Module contains utils specific for video_module but not for transcripts.
|
||||
"""
|
||||
@@ -33,6 +34,8 @@ def create_youtube_string(module):
|
||||
])
|
||||
|
||||
|
||||
# def get_video_from_cdn(cdn_base_url, original_video_url, cdn_branding_logo_url):
|
||||
# Not sure if this third variable is necessary...
|
||||
def get_video_from_cdn(cdn_base_url, original_video_url):
|
||||
"""
|
||||
Get video URL from CDN.
|
||||
@@ -46,7 +49,7 @@ def get_video_from_cdn(cdn_base_url, original_video_url):
|
||||
"http://cm12.c110.play.bokecc.com/flvs/ca/QxcVl/u39EQbA0Ra-20.mp4",
|
||||
"http://bm1.42.play.bokecc.com/flvs/ca/QxcVl/u39EQbA0Ra-20.mp4"
|
||||
],
|
||||
"s3_url": "http://s3.amazonaws.com/BESTech/CS169/download/CS169_v13_w5l2s3.mp4"
|
||||
"s3_url": "http://s3.amazonaws.com/BESTech/CS169/download/CS169_v13_w5l2s3.mp4",
|
||||
}
|
||||
where `s3_url` is requested original video url and `sources` is the list of
|
||||
alternative links.
|
||||
|
||||
9
lms/djangoapps/branding/admin.py
Normal file
9
lms/djangoapps/branding/admin.py
Normal file
@@ -0,0 +1,9 @@
|
||||
'''
|
||||
Django admin pages for Video Branding Configuration.
|
||||
'''
|
||||
from django.contrib import admin
|
||||
from config_models.admin import ConfigurationModelAdmin
|
||||
|
||||
from .models import BrandingInfoConfig
|
||||
|
||||
admin.site.register(BrandingInfoConfig, ConfigurationModelAdmin)
|
||||
74
lms/djangoapps/branding/migrations/0001_initial.py
Normal file
74
lms/djangoapps/branding/migrations/0001_initial.py
Normal file
@@ -0,0 +1,74 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import datetime
|
||||
from south.db import db
|
||||
from south.v2 import SchemaMigration
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Migration(SchemaMigration):
|
||||
|
||||
def forwards(self, orm):
|
||||
# Adding model 'BrandingInfoConfig'
|
||||
db.create_table('branding_brandinginfoconfig', (
|
||||
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
|
||||
('change_date', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
|
||||
('changed_by', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True, on_delete=models.PROTECT)),
|
||||
('enabled', self.gf('django.db.models.fields.BooleanField')(default=False)),
|
||||
('configuration', self.gf('django.db.models.fields.TextField')()),
|
||||
))
|
||||
db.send_create_signal('branding', ['BrandingInfoConfig'])
|
||||
|
||||
|
||||
def backwards(self, orm):
|
||||
# Deleting model 'BrandingInfoConfig'
|
||||
db.delete_table('branding_brandinginfoconfig')
|
||||
|
||||
|
||||
models = {
|
||||
'auth.group': {
|
||||
'Meta': {'object_name': 'Group'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
|
||||
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
|
||||
},
|
||||
'auth.permission': {
|
||||
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
|
||||
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
|
||||
},
|
||||
'auth.user': {
|
||||
'Meta': {'object_name': 'User'},
|
||||
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
|
||||
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
|
||||
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
|
||||
},
|
||||
'branding.brandinginfoconfig': {
|
||||
'Meta': {'object_name': 'BrandingInfoConfig'},
|
||||
'change_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'changed_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'on_delete': 'models.PROTECT'}),
|
||||
'configuration': ('django.db.models.fields.TextField', [], {}),
|
||||
'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
|
||||
},
|
||||
'contenttypes.contenttype': {
|
||||
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
|
||||
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['branding']
|
||||
0
lms/djangoapps/branding/migrations/__init__.py
Normal file
0
lms/djangoapps/branding/migrations/__init__.py
Normal file
46
lms/djangoapps/branding/models.py
Normal file
46
lms/djangoapps/branding/models.py
Normal file
@@ -0,0 +1,46 @@
|
||||
"""
|
||||
Model used by Video module for Branding configuration.
|
||||
|
||||
Includes:
|
||||
BrandingInfoConfig: A ConfigurationModel for managing how Video Module will
|
||||
use Branding.
|
||||
"""
|
||||
import json
|
||||
from django.db.models import TextField
|
||||
from django.core.exceptions import ValidationError
|
||||
from config_models.models import ConfigurationModel
|
||||
|
||||
|
||||
class BrandingInfoConfig(ConfigurationModel):
|
||||
"""
|
||||
Configuration for Branding.
|
||||
|
||||
Example of configuration that must be stored:
|
||||
{
|
||||
"CN": {
|
||||
"url": "http://www.xuetangx.com",
|
||||
"logo_src": "http://www.xuetangx.com/static/images/logo.png",
|
||||
"logo_tag": "Video hosted by XuetangX.com"
|
||||
}
|
||||
}
|
||||
"""
|
||||
configuration = TextField(
|
||||
help_text="JSON data of Configuration for Video Branding."
|
||||
)
|
||||
|
||||
def clean(self):
|
||||
"""
|
||||
Validates configuration text field.
|
||||
"""
|
||||
try:
|
||||
json.loads(self.configuration)
|
||||
except ValueError:
|
||||
raise ValidationError('Must be valid JSON string.')
|
||||
|
||||
@classmethod
|
||||
def get_config(cls):
|
||||
"""
|
||||
Get the Video Branding Configuration.
|
||||
"""
|
||||
info = cls.current()
|
||||
return json.loads(info.configuration) if info.enabled else {}
|
||||
0
lms/djangoapps/branding/tests/__init__.py
Normal file
0
lms/djangoapps/branding/tests/__init__.py
Normal file
58
lms/djangoapps/branding/tests/test_models.py
Normal file
58
lms/djangoapps/branding/tests/test_models.py
Normal file
@@ -0,0 +1,58 @@
|
||||
"""
|
||||
Tests for the Video Branding configuration.
|
||||
"""
|
||||
from django.test import TestCase
|
||||
from django.core.exceptions import ValidationError
|
||||
from branding.models import BrandingInfoConfig
|
||||
|
||||
|
||||
class BrandingInfoConfigTest(TestCase):
|
||||
"""
|
||||
Test the BrandingInfoConfig model.
|
||||
"""
|
||||
def setUp(self):
|
||||
self.configuration_string = """{
|
||||
"CN": {
|
||||
"url": "http://www.xuetangx.com",
|
||||
"logo_src": "http://www.xuetangx.com/static/images/logo.png",
|
||||
"logo_tag": "Video hosted by XuetangX.com"
|
||||
}
|
||||
}"""
|
||||
self.config = BrandingInfoConfig(configuration=self.configuration_string)
|
||||
|
||||
def test_create(self):
|
||||
"""
|
||||
Tests creation of configuration.
|
||||
"""
|
||||
self.config.save()
|
||||
self.assertEquals(self.config.configuration, self.configuration_string)
|
||||
|
||||
def test_clean_bad_json(self):
|
||||
"""
|
||||
Tests if bad Json string was given.
|
||||
"""
|
||||
self.config = BrandingInfoConfig(configuration='{"bad":"test"')
|
||||
self.assertRaises(ValidationError, self.config.clean)
|
||||
|
||||
def test_get(self):
|
||||
"""
|
||||
Tests get configuration from saved string.
|
||||
"""
|
||||
self.config.enabled = True
|
||||
self.config.save()
|
||||
expected_config = {
|
||||
"CN": {
|
||||
"url": "http://www.xuetangx.com",
|
||||
"logo_src": "http://www.xuetangx.com/static/images/logo.png",
|
||||
"logo_tag": "Video hosted by XuetangX.com"
|
||||
}
|
||||
}
|
||||
self.assertEquals(self.config.get_config(), expected_config)
|
||||
|
||||
def test_get_not_enabled(self):
|
||||
"""
|
||||
Tests get configuration that is not enabled.
|
||||
"""
|
||||
self.config.enabled = False
|
||||
self.config.save()
|
||||
self.assertEquals(self.config.get_config(), {})
|
||||
@@ -112,7 +112,7 @@ class AnonymousIndexPageTest(ModuleStoreTestCase):
|
||||
# Response should be instance of HttpResponseRedirect.
|
||||
self.assertIsInstance(response, HttpResponseRedirect)
|
||||
# Location should be "/login".
|
||||
self.assertEqual(response._headers.get("location")[1], "/login")
|
||||
self.assertEqual(response._headers.get("location")[1], "/login") # pylint: disable=protected-access
|
||||
|
||||
|
||||
@override_settings(MODULESTORE=TEST_DATA_MOCK_MODULESTORE)
|
||||
@@ -198,7 +198,7 @@ class IndexPageCourseCardsSortingTests(ModuleStoreTestCase):
|
||||
def test_course_cards_sorted_by_default_sorting(self):
|
||||
response = self.client.get('/')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
((template, context), _) = RENDER_MOCK.call_args
|
||||
((template, context), _) = RENDER_MOCK.call_args # pylint: disable=unpacking-non-sequence
|
||||
self.assertEqual(template, 'index.html')
|
||||
|
||||
# Now the courses will be stored in their announcement dates.
|
||||
@@ -209,7 +209,7 @@ class IndexPageCourseCardsSortingTests(ModuleStoreTestCase):
|
||||
# check the /courses view
|
||||
response = self.client.get(reverse('branding.views.courses'))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
((template, context), _) = RENDER_MOCK.call_args
|
||||
((template, context), _) = RENDER_MOCK.call_args # pylint: disable=unpacking-non-sequence
|
||||
self.assertEqual(template, 'courseware/courses.html')
|
||||
|
||||
# Now the courses will be stored in their announcement dates.
|
||||
@@ -223,7 +223,7 @@ class IndexPageCourseCardsSortingTests(ModuleStoreTestCase):
|
||||
def test_course_cards_sorted_by_start_date_show_earliest_first(self):
|
||||
response = self.client.get('/')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
((template, context), _) = RENDER_MOCK.call_args
|
||||
((template, context), _) = RENDER_MOCK.call_args # pylint: disable=unpacking-non-sequence
|
||||
self.assertEqual(template, 'index.html')
|
||||
|
||||
# now the courses will be sorted by their creation dates, earliest first.
|
||||
@@ -234,7 +234,7 @@ class IndexPageCourseCardsSortingTests(ModuleStoreTestCase):
|
||||
# check the /courses view as well
|
||||
response = self.client.get(reverse('branding.views.courses'))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
((template, context), _) = RENDER_MOCK.call_args
|
||||
((template, context), _) = RENDER_MOCK.call_args # pylint: disable=unpacking-non-sequence
|
||||
self.assertEqual(template, 'courseware/courses.html')
|
||||
|
||||
# now the courses will be sorted by their creation dates, earliest first.
|
||||
@@ -39,6 +39,7 @@ class TestVideoYouTube(TestVideo):
|
||||
expected_context = {
|
||||
'ajax_url': self.item_descriptor.xmodule_runtime.ajax_url + '/save_user_state',
|
||||
'autoplay': settings.FEATURES.get('AUTOPLAY_VIDEOS', False),
|
||||
'branding_info': None,
|
||||
'data_dir': getattr(self, 'data_dir', None),
|
||||
'display_name': u'A Name',
|
||||
'end': 3610.0,
|
||||
@@ -102,6 +103,7 @@ class TestVideoNonYouTube(TestVideo):
|
||||
|
||||
expected_context = {
|
||||
'ajax_url': self.item_descriptor.xmodule_runtime.ajax_url + '/save_user_state',
|
||||
'branding_info': None,
|
||||
'data_dir': getattr(self, 'data_dir', None),
|
||||
'show_captions': 'true',
|
||||
'handout': None,
|
||||
@@ -204,6 +206,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
|
||||
sources = json.dumps([u'example.mp4', u'example.webm'])
|
||||
|
||||
expected_context = {
|
||||
'branding_info': None,
|
||||
'data_dir': getattr(self, 'data_dir', None),
|
||||
'show_captions': 'true',
|
||||
'handout': None,
|
||||
@@ -320,6 +323,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
|
||||
]
|
||||
|
||||
initial_context = {
|
||||
'branding_info': None,
|
||||
'data_dir': getattr(self, 'data_dir', None),
|
||||
'show_captions': 'true',
|
||||
'handout': None,
|
||||
@@ -459,6 +463,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
|
||||
|
||||
# Video found for edx_video_id
|
||||
initial_context = {
|
||||
'branding_info': None,
|
||||
'data_dir': getattr(self, 'data_dir', None),
|
||||
'show_captions': 'true',
|
||||
'handout': None,
|
||||
@@ -576,6 +581,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
|
||||
|
||||
# Video found for edx_video_id
|
||||
initial_context = {
|
||||
'branding_info': None,
|
||||
'data_dir': getattr(self, 'data_dir', None),
|
||||
'show_captions': 'true',
|
||||
'handout': None,
|
||||
@@ -628,11 +634,22 @@ class TestGetHtmlMethod(BaseTestXmodule):
|
||||
self.item_descriptor.xmodule_runtime.render_template('video.html', expected_context)
|
||||
)
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
@patch('xmodule.video_module.video_module.BrandingInfoConfig')
|
||||
@patch('xmodule.video_module.video_module.get_video_from_cdn')
|
||||
def test_get_html_cdn_source(self, mocked_get_video):
|
||||
def test_get_html_cdn_source(self, mocked_get_video, mock_BrandingInfoConfig):
|
||||
"""
|
||||
Test if sources got from CDN.
|
||||
"""
|
||||
|
||||
mock_BrandingInfoConfig.get_config.return_value = {
|
||||
"CN": {
|
||||
'url': 'http://www.xuetangx.com',
|
||||
'logo_src': 'http://www.xuetangx.com/static/images/logo.png',
|
||||
'logo_tag': 'Video hosted by XuetangX.com'
|
||||
}
|
||||
}
|
||||
|
||||
def side_effect(*args, **kwargs):
|
||||
cdn = {
|
||||
'http://example.com/example.mp4': 'http://cdn_example.com/example.mp4',
|
||||
@@ -679,6 +696,11 @@ class TestGetHtmlMethod(BaseTestXmodule):
|
||||
]
|
||||
|
||||
initial_context = {
|
||||
'branding_info': {
|
||||
'logo_src': 'http://www.xuetangx.com/static/images/logo.png',
|
||||
'logo_tag': 'Video hosted by XuetangX.com',
|
||||
'url': 'http://www.xuetangx.com'
|
||||
},
|
||||
'data_dir': getattr(self, 'data_dir', None),
|
||||
'show_captions': 'true',
|
||||
'handout': None,
|
||||
|
||||
@@ -1568,6 +1568,7 @@ INSTALLED_APPS = (
|
||||
'licenses',
|
||||
'openedx.core.djangoapps.course_groups',
|
||||
'bulk_email',
|
||||
'branding',
|
||||
|
||||
# External auth (OpenID, shib)
|
||||
'external_auth',
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
This config file runs the simplest dev environment using sqlite, and db-based
|
||||
sessions. Assumes structure:
|
||||
|
||||
@@ -121,8 +121,8 @@
|
||||
% else:
|
||||
<li class="a11y-menu-item">
|
||||
% endif
|
||||
## This is necessary so we don't scrape 'display_name' as a string.
|
||||
<% dname = item['display_name'] %>
|
||||
## This is necessary so we don't scrape 'display_name' as a string.
|
||||
<% dname = item['display_name'] %>
|
||||
<a class="a11y-menu-item-link" href="#${item['value']}" title="${_(dname)}" data-value="${item['value']}">
|
||||
${_(dname)}
|
||||
</a>
|
||||
@@ -141,5 +141,12 @@
|
||||
${('<a href="%s" target="_blank">' + _('Download Handout') + '</a>') % handout}
|
||||
</li>
|
||||
% endif
|
||||
|
||||
% if branding_info:
|
||||
<li id="branding" class="branding">
|
||||
<span class="host-tag">${branding_info['logo_tag']}</span>
|
||||
<a href="${branding_info['url']}" target="_blank" title="${branding_info['logo_tag']}"><img class="brand-logo" src="${branding_info['logo_src']}" alt="${branding_info['logo_tag']}" /></a>
|
||||
</li>
|
||||
% endif
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user