diff --git a/common/lib/xmodule/xmodule/tests/test_xblock_wrappers.py b/common/lib/xmodule/xmodule/tests/test_xblock_wrappers.py index 4e7112e171..d82913e16a 100644 --- a/common/lib/xmodule/xmodule/tests/test_xblock_wrappers.py +++ b/common/lib/xmodule/xmodule/tests/test_xblock_wrappers.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """ Tests for the wrapping layer that provides the XBlock API using XModule/Descriptor functionality @@ -27,7 +28,7 @@ from xblock.core import XBlock from opaque_keys.edx.locator import BlockUsageLocator, CourseLocator -from xmodule.x_module import ModuleSystem, XModule, XModuleDescriptor, DescriptorSystem, STUDENT_VIEW, STUDIO_VIEW +from xmodule.x_module import ModuleSystem, XModule, XModuleDescriptor, DescriptorSystem, STUDENT_VIEW, STUDIO_VIEW, PUBLIC_VIEW from xmodule.annotatable_module import AnnotatableDescriptor from xmodule.capa_module import CapaDescriptor from xmodule.course_module import CourseDescriptor @@ -63,8 +64,8 @@ LEAF_XMODULES = { CONTAINER_XMODULES = { ConditionalDescriptor: [{}], CourseDescriptor: [{}], - RandomizeDescriptor: [{}], - SequenceDescriptor: [{}], + RandomizeDescriptor: [{'display_name': 'Test String Display'}], + SequenceDescriptor: [{'display_name': u'Test Unicode हिंदी Display'}], VerticalBlock: [{}], WrapperBlock: [{}], } @@ -433,3 +434,34 @@ class TestXmlExport(XBlockWrapperTestMixin, TestCase): self.assertEquals(list(xmodule_api_fs.walk()), list(xblock_api_fs.walk())) self.assertEquals(etree.tostring(xmodule_node), etree.tostring(xblock_node)) + + +class TestPublicView(XBlockWrapperTestMixin, TestCase): + """ + This tests that default public_view shows the correct message. + """ + shard = 1 + + def skip_if_invalid(self, descriptor_cls): + pure_xblock_class = issubclass(descriptor_cls, XBlock) and not issubclass(descriptor_cls, XModuleDescriptor) + if pure_xblock_class: + public_view = descriptor_cls.public_view + else: + public_view = descriptor_cls.module_class.public_view + if public_view != XModule.public_view: + raise SkipTest(descriptor_cls.__name__ + " implements public_view") + + def check_property(self, descriptor): + """ + Assert that public_view contains correct message. + """ + if descriptor.display_name: + self.assertIn( + descriptor.display_name, + descriptor.render(PUBLIC_VIEW).content + ) + else: + self.assertIn( + "This content is only accessible", + descriptor.render(PUBLIC_VIEW).content + ) diff --git a/common/lib/xmodule/xmodule/x_module.py b/common/lib/xmodule/xmodule/x_module.py index 299a3e3d8b..521d2fd445 100644 --- a/common/lib/xmodule/xmodule/x_module.py +++ b/common/lib/xmodule/xmodule/x_module.py @@ -72,7 +72,10 @@ STUDIO_VIEW = 'studio_view' # Views that present a "preview" view of an xblock (as opposed to an editing view). PREVIEW_VIEWS = [STUDENT_VIEW, PUBLIC_VIEW, AUTHOR_VIEW] -DEFAULT_PUBLIC_VIEW_MESSAGE = u'Please enroll to view this content.' +DEFAULT_PUBLIC_VIEW_MESSAGE = ( + u'This content is only accessible to enrolled learners. ' + u'Sign in or register, and enroll in this course to view it.' +) # Make '_' a no-op so we can scrape strings. Using lambda instead of # `django.utils.translation.ugettext_noop` because Django cannot be imported in this file @@ -766,7 +769,18 @@ class XModuleMixin(XModuleFields, XBlock): u'' u'
' ) - return Fragment(alert_html.format(DEFAULT_PUBLIC_VIEW_MESSAGE)) + + if self.display_name: + display_text = _( + u'{display_name} is only accessible to enrolled learners. ' + 'Sign in or register, and enroll in this course to view it.' + ).format( + display_name=self.display_name + ) + else: + display_text = _(DEFAULT_PUBLIC_VIEW_MESSAGE) + + return Fragment(alert_html.format(display_text)) class ProxyAttribute(object): diff --git a/lms/djangoapps/course_blocks/transformers/library_content.py b/lms/djangoapps/course_blocks/transformers/library_content.py index eb893a459d..b92b58dbeb 100644 --- a/lms/djangoapps/course_blocks/transformers/library_content.py +++ b/lms/djangoapps/course_blocks/transformers/library_content.py @@ -98,7 +98,7 @@ class ContentLibraryTransformer(FilteringTransformerMixin, BlockStructureTransfo # Save back any changes if any(block_keys[changed] for changed in ('invalid', 'overlimit', 'added')): state_dict['selected'] = list(selected) - StudentModule.objects.update_or_create( + StudentModule.save_state( # pylint: disable=no-value-for-parameter student=usage_info.user, course_id=usage_info.course_key, module_state_key=block_key, diff --git a/lms/djangoapps/course_blocks/utils.py b/lms/djangoapps/course_blocks/utils.py index b2db403d0b..b36a691477 100644 --- a/lms/djangoapps/course_blocks/utils.py +++ b/lms/djangoapps/course_blocks/utils.py @@ -18,6 +18,9 @@ def get_student_module_as_dict(user, course_key, block_key): Returns: StudentModule as a (possibly empty) dict. """ + if not user.is_authenticated(): + return {} + try: student_module = StudentModule.objects.get( student=user, diff --git a/lms/djangoapps/courseware/courses.py b/lms/djangoapps/courseware/courses.py index 8a3266a12a..cc6ee05f2e 100644 --- a/lms/djangoapps/courseware/courses.py +++ b/lms/djangoapps/courseware/courses.py @@ -38,6 +38,7 @@ from opaque_keys.edx.keys import UsageKey from openedx.core.djangoapps.content.course_overviews.models import CourseOverview from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers from openedx.core.lib.api.view_utils import LazySequence +from openedx.features.course_experience import COURSE_ENABLE_UNENROLLED_ACCESS_FLAG from path import Path as path from six import text_type from static_replace import replace_static_urls @@ -643,3 +644,13 @@ def get_course_chapter_ids(course_key): log.exception('Failed to retrieve course from modulestore.') return [] return [unicode(chapter_key) for chapter_key in chapter_keys if chapter_key.block_type == 'chapter'] + + +def allow_public_access(course, visibilities): + """ + This checks if the unenrolled access waffle flag for the course is set + and the course visibility matches any of the input visibilities. + """ + unenrolled_access_flag = COURSE_ENABLE_UNENROLLED_ACCESS_FLAG.is_enabled(course.id) + allow_access = unenrolled_access_flag and course.course_visibility in visibilities + return allow_access diff --git a/lms/djangoapps/courseware/models.py b/lms/djangoapps/courseware/models.py index abeff61837..d54fcb96df 100644 --- a/lms/djangoapps/courseware/models.py +++ b/lms/djangoapps/courseware/models.py @@ -161,6 +161,18 @@ class StudentModule(models.Model): module_states = module_states.filter(student_id=student_id) return module_states + @classmethod + def save_state(cls, student, course_id, module_state_key, defaults): + if not student.is_authenticated(): + return + else: + cls.objects.update_or_create( + student=student, + course_id=course_id, + module_state_key=module_state_key, + defaults=defaults, + ) + class BaseStudentModuleHistory(models.Model): """Abstract class containing most fields used by any class diff --git a/lms/djangoapps/courseware/tests/test_about.py b/lms/djangoapps/courseware/tests/test_about.py index 2e1892d95d..5108de1379 100644 --- a/lms/djangoapps/courseware/tests/test_about.py +++ b/lms/djangoapps/courseware/tests/test_about.py @@ -3,6 +3,7 @@ Test the about xblock """ import datetime import ddt +import mock import pytz from ccx_keys.locator import CCXLocator from django.conf import settings @@ -16,14 +17,22 @@ from waffle.testutils import override_switch from course_modes.models import CourseMode from lms.djangoapps.ccx.tests.factories import CcxFactory from openedx.core.lib.tests import attr +from openedx.core.djangoapps.waffle_utils.testutils import override_waffle_flag from openedx.features.course_experience.waffle import WAFFLE_NAMESPACE as COURSE_EXPERIENCE_WAFFLE_NAMESPACE from openedx.features.course_experience.waffle import ENABLE_COURSE_ABOUT_SIDEBAR_HTML +from openedx.features.course_experience import COURSE_ENABLE_UNENROLLED_ACCESS_FLAG from shoppingcart.models import Order, PaidCourseRegistration from student.models import CourseEnrollment from student.tests.factories import AdminFactory, CourseEnrollmentAllowedFactory, UserFactory from track.tests import EventTrackingTestCase from util.milestones_helpers import get_prerequisite_courses_display, set_prerequisite_courses -from xmodule.course_module import CATALOG_VISIBILITY_ABOUT, CATALOG_VISIBILITY_NONE +from xmodule.course_module import ( + CATALOG_VISIBILITY_ABOUT, + CATALOG_VISIBILITY_NONE, + COURSE_VISIBILITY_PRIVATE, + COURSE_VISIBILITY_PUBLIC_OUTLINE, + COURSE_VISIBILITY_PUBLIC +) from xmodule.modulestore.tests.django_utils import ( TEST_DATA_MIXED_MODULESTORE, TEST_DATA_SPLIT_MODULESTORE, @@ -41,6 +50,7 @@ REG_STR = "