diff --git a/cms/static/js/spec/views/pages/container_subviews_spec.js b/cms/static/js/spec/views/pages/container_subviews_spec.js
index 8ac10470ed..4f666fce77 100644
--- a/cms/static/js/spec/views/pages/container_subviews_spec.js
+++ b/cms/static/js/spec/views/pages/container_subviews_spec.js
@@ -78,15 +78,16 @@ define(["jquery", "underscore", "underscore.string", "js/common_helpers/ajax_hel
respondWithJson(json);
};
- describe("PreviewActionController", function () {
+ describe("ViewLiveButtonController", function () {
var viewPublishedCss = '.button-view',
- previewCss = '.button-preview',
visibilityNoteCss = '.note-visibility';
it('renders correctly for unscheduled unit', function () {
renderContainerPage(this, mockContainerXBlockHtml);
expect(containerPage.$(viewPublishedCss)).toHaveClass(disabledCss);
- expect(containerPage.$(previewCss)).not.toHaveClass(disabledCss);
+ expect(containerPage.$(viewPublishedCss).attr('title')).toBe("Open the courseware in the LMS");
+ expect(containerPage.$('.button-preview')).not.toHaveClass(disabledCss);
+ expect(containerPage.$('.button-preview').attr('title')).toBe("Preview the courseware in the LMS");
});
it('updates when publish state changes', function () {
@@ -98,19 +99,6 @@ define(["jquery", "underscore", "underscore.string", "js/common_helpers/ajax_hel
expect(containerPage.$(viewPublishedCss)).toHaveClass(disabledCss);
});
- it('updates when has_changes attribute changes', function () {
- renderContainerPage(this, mockContainerXBlockHtml);
- fetch({has_changes: true});
- expect(containerPage.$(previewCss)).not.toHaveClass(disabledCss);
-
- fetch({published: true, has_changes: false});
- expect(containerPage.$(previewCss)).toHaveClass(disabledCss);
-
- // If published is false, preview is always enabled.
- fetch({published: false, has_changes: false});
- expect(containerPage.$(previewCss)).not.toHaveClass(disabledCss);
- });
-
it('updates when has_content_group_components attribute changes', function () {
renderContainerPage(this, mockContainerXBlockHtml);
fetch({has_content_group_components: false});
diff --git a/cms/static/js/views/pages/container.js b/cms/static/js/views/pages/container.js
index 2b071c2d99..595a3f95e7 100644
--- a/cms/static/js/views/pages/container.js
+++ b/cms/static/js/views/pages/container.js
@@ -68,11 +68,11 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views
});
this.publishHistory.render();
- this.previewActions = new ContainerSubviews.PreviewActionController({
+ this.viewLiveActions = new ContainerSubviews.ViewLiveButtonController({
el: this.$('.nav-actions'),
model: this.model
});
- this.previewActions.render();
+ this.viewLiveActions.render();
this.unitOutlineView = new UnitOutlineView({
el: this.$('.wrapper-unit-overview'),
diff --git a/cms/static/js/views/pages/container_subviews.js b/cms/static/js/views/pages/container_subviews.js
index 441a4d6860..378e100da4 100644
--- a/cms/static/js/views/pages/container_subviews.js
+++ b/cms/static/js/views/pages/container_subviews.js
@@ -50,28 +50,21 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/views/utils/
});
/**
- * A controller for updating the "View Live" and "Preview" buttons.
+ * A controller for updating the "View Live" button.
*/
- var PreviewActionController = ContainerStateListenerView.extend({
+ var ViewLiveButtonController = ContainerStateListenerView.extend({
shouldRefresh: function(model) {
- return ViewUtils.hasChangedAttributes(model, ['has_changes', 'published']);
+ return ViewUtils.hasChangedAttributes(model, ['published']);
},
render: function() {
- var previewAction = this.$el.find('.button-preview'),
- viewLiveAction = this.$el.find('.button-view');
+ var viewLiveAction = this.$el.find('.button-view');
if (this.model.get('published')) {
viewLiveAction.removeClass(disabledCss).attr('aria-disabled', false);
}
else {
viewLiveAction.addClass(disabledCss).attr('aria-disabled', true);
}
- if (this.model.get('has_changes') || !this.model.get('published')) {
- previewAction.removeClass(disabledCss).attr('aria-disabled', false);
- }
- else {
- previewAction.addClass(disabledCss).attr('aria-disabled', true);
- }
}
});
@@ -256,7 +249,7 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/views/utils/
return {
'MessageView': MessageView,
- 'PreviewActionController': PreviewActionController,
+ 'ViewLiveButtonController': ViewLiveButtonController,
'Publisher': Publisher,
'PublishHistory': PublishHistory
};
diff --git a/cms/templates/container.html b/cms/templates/container.html
index 3fd47ab52c..b93ada88c9 100644
--- a/cms/templates/container.html
+++ b/cms/templates/container.html
@@ -69,13 +69,13 @@ from django.utils.translation import ugettext as _
% if is_unit_page:
-
-
+
${_("View Live Version")}
-
-
- ${_("Preview Changes")}
+
+ ${_("Preview")}
% else:
diff --git a/cms/templates/js/mock/mock-container-page.underscore b/cms/templates/js/mock/mock-container-page.underscore
index 6a4474d6bd..d5356605f2 100644
--- a/cms/templates/js/mock/mock-container-page.underscore
+++ b/cms/templates/js/mock/mock-container-page.underscore
@@ -16,13 +16,13 @@
% if is_unit_page:
-
-
+
View Live Version
-
-
- Preview Changes
+
+ Preview
% else:
diff --git a/cms/templates/ux/reference/container.html b/cms/templates/ux/reference/container.html
index 7345641536..81821b503a 100644
--- a/cms/templates/ux/reference/container.html
+++ b/cms/templates/ux/reference/container.html
@@ -46,7 +46,7 @@
-
- Preview Changes
+ Preview
diff --git a/common/test/acceptance/pages/studio/container.py b/common/test/acceptance/pages/studio/container.py
index b06b6956be..aa46ce72ca 100644
--- a/common/test/acceptance/pages/studio/container.py
+++ b/common/test/acceptance/pages/studio/container.py
@@ -209,7 +209,7 @@ class ContainerPage(PageObject):
def preview(self):
"""
- Clicks "Preview Changes", which will open the draft version of the unit page in the LMS.
+ Clicks "Preview", which will open the draft version of the unit page in the LMS.
Switches the browser to the newly opened LMS window.
"""
diff --git a/lms/djangoapps/courseware/access.py b/lms/djangoapps/courseware/access.py
index 759dff19cc..2377335a52 100644
--- a/lms/djangoapps/courseware/access.py
+++ b/lms/djangoapps/courseware/access.py
@@ -22,6 +22,7 @@ from xmodule.error_module import ErrorDescriptor
from xmodule.x_module import XModule, DEPRECATION_VSCOMPAT_EVENT
from xmodule.split_test_module import get_split_user_partitions
from xmodule.partitions.partitions import NoSuchUserPartitionError, NoSuchUserPartitionGroupError
+from xmodule.util.django import get_current_request_hostname
from external_auth.models import ExternalAuthMap
from courseware.masquerade import get_masquerade_role, is_masquerading_as_student
@@ -436,7 +437,7 @@ def _has_access_descriptor(user, action, descriptor, course_key=None):
descriptor,
course_key=course_key
)
- if now > effective_start:
+ if in_preview_mode() or now > effective_start:
# after start date, everyone can see it
debug("Allow: now > effective start date")
return True
@@ -687,3 +688,11 @@ def get_user_role(user, course_key):
return 'staff'
else:
return 'student'
+
+
+def in_preview_mode():
+ """
+ Returns whether the user is in preview mode or not.
+ """
+ hostname = get_current_request_hostname()
+ return hostname and settings.PREVIEW_DOMAIN in hostname.split('.')
diff --git a/lms/djangoapps/courseware/tests/test_access.py b/lms/djangoapps/courseware/tests/test_access.py
index bf0daeb872..b85fb049fa 100644
--- a/lms/djangoapps/courseware/tests/test_access.py
+++ b/lms/djangoapps/courseware/tests/test_access.py
@@ -42,6 +42,16 @@ class AccessTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase):
self.course_staff = StaffFactory(course_key=self.course.course_key)
self.course_instructor = InstructorFactory(course_key=self.course.course_key)
+ def verify_access(self, mock_unit, student_should_have_access):
+ """ Verify the expected result from _has_access_descriptor """
+ self.assertEqual(
+ student_should_have_access,
+ access._has_access_descriptor(self.anonymous_user, 'load', mock_unit, course_key=self.course.course_key)
+ )
+ self.assertTrue(
+ access._has_access_descriptor(self.course_staff, 'load', mock_unit, course_key=self.course.course_key)
+ )
+
def test_has_access_to_course(self):
self.assertFalse(access._has_access_to_course(
None, 'staff', self.course.course_key
@@ -132,41 +142,73 @@ class AccessTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase):
mock_unit = Mock(user_partitions=[])
mock_unit._class_tags = {} # Needed for detached check in _has_access_descriptor
- def verify_access(student_should_have_access):
- """ Verify the expected result from _has_access_descriptor """
- self.assertEqual(student_should_have_access, access._has_access_descriptor(
- self.anonymous_user, 'load', mock_unit, course_key=self.course.course_key)
- )
- # staff always has access
- self.assertTrue(access._has_access_descriptor(
- self.course_staff, 'load', mock_unit, course_key=self.course.course_key)
- )
-
# No start date, staff lock on
mock_unit.visible_to_staff_only = True
- verify_access(False)
+ self.verify_access(mock_unit, False)
# No start date, staff lock off.
mock_unit.visible_to_staff_only = False
- verify_access(True)
+ self.verify_access(mock_unit, True)
# Start date in the past, staff lock on.
mock_unit.start = datetime.datetime.now(pytz.utc) - datetime.timedelta(days=1)
mock_unit.visible_to_staff_only = True
- verify_access(False)
+ self.verify_access(mock_unit, False)
# Start date in the past, staff lock off.
mock_unit.visible_to_staff_only = False
- verify_access(True)
+ self.verify_access(mock_unit, True)
# Start date in the future, staff lock on.
mock_unit.start = datetime.datetime.now(pytz.utc) + datetime.timedelta(days=1) # release date in the future
mock_unit.visible_to_staff_only = True
- verify_access(False)
+ self.verify_access(mock_unit, False)
# Start date in the future, staff lock off.
mock_unit.visible_to_staff_only = False
- verify_access(False)
+ self.verify_access(mock_unit, False)
+
+ @patch.dict('django.conf.settings.FEATURES', {'DISABLE_START_DATES': False})
+ @patch('courseware.access.get_current_request_hostname', Mock(return_value='preview.localhost'))
+ def test__has_access_descriptor_in_preview_mode(self):
+ """
+ Tests that descriptor has access in preview mode.
+ """
+ mock_unit = Mock(user_partitions=[])
+ mock_unit._class_tags = {} # Needed for detached check in _has_access_descriptor
+
+ # No start date.
+ mock_unit.visible_to_staff_only = False
+ self.verify_access(mock_unit, True)
+
+ # Start date in the past.
+ mock_unit.start = datetime.datetime.now(pytz.utc) - datetime.timedelta(days=1)
+ self.verify_access(mock_unit, True)
+
+ # Start date in the future.
+ mock_unit.start = datetime.datetime.now(pytz.utc) + datetime.timedelta(days=1) # release date in the future
+ self.verify_access(mock_unit, True)
+
+ @patch.dict('django.conf.settings.FEATURES', {'DISABLE_START_DATES': False})
+ @patch('courseware.access.get_current_request_hostname', Mock(return_value='localhost'))
+ def test__has_access_descriptor_when_not_in_preview_mode(self):
+ """
+ Tests that descriptor has no access when start date in future & without preview.
+ """
+ mock_unit = Mock(user_partitions=[])
+ mock_unit._class_tags = {} # Needed for detached check in _has_access_descriptor
+
+ # No start date.
+ mock_unit.visible_to_staff_only = False
+ self.verify_access(mock_unit, True)
+
+ # Start date in the past.
+ mock_unit.start = datetime.datetime.now(pytz.utc) - datetime.timedelta(days=1)
+ self.verify_access(mock_unit, True)
+
+ # Start date in the future.
+ mock_unit.start = datetime.datetime.now(pytz.utc) + datetime.timedelta(days=1) # release date in the future
+ self.verify_access(mock_unit, False)
def test__has_access_course_desc_can_enroll(self):
yesterday = datetime.datetime.now(pytz.utc) - datetime.timedelta(days=1)
diff --git a/lms/djangoapps/courseware/views.py b/lms/djangoapps/courseware/views.py
index a0566fbb7d..fb1c5ba945 100644
--- a/lms/djangoapps/courseware/views.py
+++ b/lms/djangoapps/courseware/views.py
@@ -31,7 +31,7 @@ from django.db import transaction
from markupsafe import escape
from courseware import grades
-from courseware.access import has_access, _adjust_start_date_for_beta_testers
+from courseware.access import has_access, in_preview_mode, _adjust_start_date_for_beta_testers
from courseware.courses import (
get_courses, get_course,
get_studio_url, get_course_with_access,
@@ -427,7 +427,7 @@ def _index_bulk_op(request, course_key, chapter, section, position):
now = datetime.now(UTC())
effective_start = _adjust_start_date_for_beta_testers(user, course, course_key)
- if staff_access and now < effective_start:
+ if not in_preview_mode() and staff_access and now < effective_start:
# Disable student view button if user is staff and
# course is not yet visible to students.
context['disable_student_access'] = True
@@ -702,7 +702,7 @@ def course_info(request, course_id):
now = datetime.now(UTC())
effective_start = _adjust_start_date_for_beta_testers(request.user, course, course_key)
- if staff_access and now < effective_start:
+ if not in_preview_mode() and staff_access and now < effective_start:
# Disable student view button if user is staff and
# course is not yet visible to students.
context['disable_student_access'] = True
diff --git a/lms/envs/common.py b/lms/envs/common.py
index 99add8d9b2..7964ce7feb 100644
--- a/lms/envs/common.py
+++ b/lms/envs/common.py
@@ -2295,3 +2295,6 @@ PROFILE_IMAGE_DEFAULT_FILE_EXTENSION = 'png'
PROFILE_IMAGE_SECRET_KEY = 'placeholder secret key'
PROFILE_IMAGE_MAX_BYTES = 1024 * 1024
PROFILE_IMAGE_MIN_BYTES = 100
+
+# This is to check the domain in case of preview.
+PREVIEW_DOMAIN = 'preview'