diff --git a/cms/djangoapps/models/settings/course_metadata.py b/cms/djangoapps/models/settings/course_metadata.py index d9981e5740..ff3fb46616 100644 --- a/cms/djangoapps/models/settings/course_metadata.py +++ b/cms/djangoapps/models/settings/course_metadata.py @@ -9,8 +9,6 @@ from xblock.fields import Scope from xblock_django.models import XBlockStudioConfigurationFlag from xmodule.modulestore.django import modulestore -from openedx.features.course_experience import COURSE_ENABLE_UNENROLLED_ACCESS_FLAG - class CourseMetadata(object): ''' @@ -65,7 +63,7 @@ class CourseMetadata(object): ] @classmethod - def filtered_list(cls, course_key=None): + def filtered_list(cls): """ Filter fields based on feature flag, i.e. enabled, disabled. """ @@ -119,10 +117,6 @@ class CourseMetadata(object): if not XBlockStudioConfigurationFlag.is_enabled(): filtered_list.append('allow_unsupported_xblocks') - # Do not show "Course Visibility For Unenrolled Learners" in Studio Advanced Settings - # if the enable_anonymous_access flag is not enabled - if not COURSE_ENABLE_UNENROLLED_ACCESS_FLAG.is_enabled(course_key=course_key): - filtered_list.append('course_visibility') return filtered_list @classmethod @@ -134,7 +128,7 @@ class CourseMetadata(object): result = {} metadata = cls.fetch_all(descriptor) for key, value in metadata.iteritems(): - if key in cls.filtered_list(descriptor.id): + if key in cls.filtered_list(): continue result[key] = value return result @@ -169,7 +163,7 @@ class CourseMetadata(object): Ensures none of the fields are in the blacklist. """ - filtered_list = cls.filtered_list(descriptor.id) + filtered_list = cls.filtered_list() # Don't filter on the tab attribute if filter_tabs is False. if not filter_tabs: filtered_list.remove("tabs") @@ -205,7 +199,7 @@ class CourseMetadata(object): errors: list of error objects result: the updated course metadata or None if error """ - filtered_list = cls.filtered_list(descriptor.id) + filtered_list = cls.filtered_list() if not filter_tabs: filtered_list.remove("tabs") diff --git a/common/lib/xmodule/xmodule/course_module.py b/common/lib/xmodule/xmodule/course_module.py index 1471489977..574058e5f1 100644 --- a/common/lib/xmodule/xmodule/course_module.py +++ b/common/lib/xmodule/xmodule/course_module.py @@ -44,10 +44,6 @@ DEFAULT_COURSE_VISIBILITY_IN_CATALOG = getattr( DEFAULT_MOBILE_AVAILABLE = getattr(settings, 'DEFAULT_MOBILE_AVAILABLE', False) -COURSE_VISIBILITY_PRIVATE = 'private' -COURSE_VISIBILITY_PUBLIC_OUTLINE = 'public_outline' -COURSE_VISIBILITY_PUBLIC = 'public' - class StringOrDate(Date): def from_json(self, value): @@ -818,21 +814,6 @@ class CourseFields(object): scope=Scope.settings ) - course_visibility = String( - display_name=_("Course Visibility For Unenrolled Learners"), - help=_( - "Defines the access permissions for unenrolled learners. This can be set to one of three values: " - "'private' (default visibility, only allowed for enrolled students), 'public_outline' " - "(allow access to course outline) and 'public' (allow access to both outline and course content)." - ), - default=COURSE_VISIBILITY_PRIVATE, - scope=Scope.settings, - values=[ - {"display_name": _("private"), "value": COURSE_VISIBILITY_PRIVATE}, - {"display_name": _("public_outline"), "value": COURSE_VISIBILITY_PUBLIC_OUTLINE}, - {"display_name": _("public"), "value": COURSE_VISIBILITY_PUBLIC}] - ) - """ instructor_info dict structure: { diff --git a/common/lib/xmodule/xmodule/html_module.py b/common/lib/xmodule/xmodule/html_module.py index de66aee0c1..6eaafbe38c 100644 --- a/common/lib/xmodule/xmodule/html_module.py +++ b/common/lib/xmodule/xmodule/html_module.py @@ -79,13 +79,6 @@ class HtmlBlock(object): """ return Fragment(self.get_html()) - @XBlock.supports("multi_device") - def public_view(self, context): - """ - Returns a fragment that contains the html for the preview view - """ - return self.student_view(context) - def student_view_data(self, context=None): # pylint: disable=unused-argument """ Return a JSON representation of the student_view of this XBlock. diff --git a/common/lib/xmodule/xmodule/seq_module.py b/common/lib/xmodule/xmodule/seq_module.py index dfb79e2ac7..2cd8d29114 100644 --- a/common/lib/xmodule/xmodule/seq_module.py +++ b/common/lib/xmodule/xmodule/seq_module.py @@ -22,7 +22,7 @@ from .exceptions import NotFoundError from .fields import Date from .mako_module import MakoModuleDescriptor from .progress import Progress -from .x_module import STUDENT_VIEW, PUBLIC_VIEW, XModule +from .x_module import STUDENT_VIEW, XModule from .xml_module import XmlDescriptor log = logging.getLogger(__name__) @@ -247,13 +247,7 @@ class SequenceModule(SequenceFields, ProctoringFields, XModule): banner_text, special_html = special_html_view if special_html and not masquerading_as_specific_student: return Fragment(special_html) - return self._student_or_public_view(context, banner_text) - - def public_view(self, context): - """ - Renders the preview view of the block in the LMS. - """ - return self._student_or_public_view(context or {}, None, PUBLIC_VIEW) + return self._student_view(context, banner_text) def _special_exam_student_view(self): """ @@ -305,7 +299,7 @@ class SequenceModule(SequenceFields, ProctoringFields, XModule): # NOTE (CCB): We default to true to maintain the behavior in place prior to allowing anonymous access access. return context.get('user_authenticated', True) - def _student_or_public_view(self, context, banner_text=None, view=STUDENT_VIEW): + def _student_view(self, context, banner_text=None): """ Returns the rendered student view of the content of this sequential. If banner_text is given, it is added to the @@ -326,9 +320,8 @@ class SequenceModule(SequenceFields, ProctoringFields, XModule): banner_text = _('This section is a prerequisite. You must complete this section in order to unlock additional content.') fragment = Fragment() - items = self._render_student_view_for_items(context, display_items, fragment, view) if prereq_met else [] params = { - 'items': items, + 'items': self._render_student_view_for_items(context, display_items, fragment) if prereq_met else [], 'element_id': self.location.html_id(), 'item_id': text_type(self.location), 'position': self.position, @@ -337,8 +330,8 @@ class SequenceModule(SequenceFields, ProctoringFields, XModule): 'next_url': context.get('next_url'), 'prev_url': context.get('prev_url'), 'banner_text': banner_text, - 'save_position': view != PUBLIC_VIEW, - 'show_completion': view != PUBLIC_VIEW, + 'save_position': self.is_user_authenticated(context), + 'show_completion': self.is_user_authenticated(context), 'gated_content': self._get_gated_content_info(prereq_met, prereq_meta_info) } fragment.add_content(self.system.render_template("seq_module.html", params)) @@ -430,7 +423,7 @@ class SequenceModule(SequenceFields, ProctoringFields, XModule): elif self.position is None or self.position > number_of_display_items: self.position = 1 - def _render_student_view_for_items(self, context, display_items, fragment, view=STUDENT_VIEW): + def _render_student_view_for_items(self, context, display_items, fragment): """ Updates the given fragment with rendered student views of the given display_items. Returns a list of dict objects with information about @@ -468,7 +461,7 @@ class SequenceModule(SequenceFields, ProctoringFields, XModule): context['show_bookmark_button'] = show_bookmark_button context['bookmarked'] = is_bookmarked - rendered_item = item.render(view, context) + rendered_item = item.render(STUDENT_VIEW, context) fragment.add_fragment_resources(rendered_item) iteminfo = { diff --git a/common/lib/xmodule/xmodule/tests/helpers.py b/common/lib/xmodule/xmodule/tests/helpers.py index 732dff94f8..9cb4decab1 100644 --- a/common/lib/xmodule/xmodule/tests/helpers.py +++ b/common/lib/xmodule/xmodule/tests/helpers.py @@ -32,19 +32,10 @@ class StubUserService(UserService): """ Stub UserService for testing the sequence module. """ - - def __init__(self, is_anonymous=False, **kwargs): - self.is_anonymous = is_anonymous - super(StubUserService, self).__init__(**kwargs) - def get_current_user(self): """ Implements abstract method for getting the current user. """ user = XBlockUser() - if self.is_anonymous: - user.opt_attrs['edx-platform.username'] = 'anonymous' - user.opt_attrs['edx-platform.is_authenticated'] = False - else: - user.opt_attrs['edx-platform.username'] = 'bilbo' + user.opt_attrs['edx-platform.username'] = 'bilbo' return user diff --git a/common/lib/xmodule/xmodule/tests/test_html_module.py b/common/lib/xmodule/xmodule/tests/test_html_module.py index 6d29a11071..819cc01f82 100644 --- a/common/lib/xmodule/xmodule/tests/test_html_module.py +++ b/common/lib/xmodule/xmodule/tests/test_html_module.py @@ -11,7 +11,6 @@ from xblock.fields import ScopeIds from xmodule.html_module import CourseInfoModule, HtmlDescriptor, HtmlModule from . import get_test_descriptor_system, get_test_system -from ..x_module import PUBLIC_VIEW, STUDENT_VIEW def instantiate_descriptor(**field_data): @@ -82,22 +81,6 @@ class HtmlModuleCourseApiTestCase(unittest.TestCase): module = HtmlModule(descriptor, module_system, field_data, Mock()) self.assertEqual(module.student_view_data(), dict(enabled=True, html=html)) - @ddt.data( - STUDENT_VIEW, - PUBLIC_VIEW, - ) - def test_student_preview_view(self, view): - """ - Ensure that student_view and public_view renders correctly. - """ - html = '
This is a test
' - descriptor = Mock() - field_data = DictFieldData({'data': html}) - module_system = get_test_system() - module = HtmlModule(descriptor, module_system, field_data, Mock()) - rendered = module_system.render(module, view, {}).content - self.assertIn(html, rendered) - class HtmlModuleSubstitutionTestCase(unittest.TestCase): descriptor = Mock() diff --git a/common/lib/xmodule/xmodule/tests/test_sequence.py b/common/lib/xmodule/xmodule/tests/test_sequence.py index dc28208ff8..ea739e731d 100644 --- a/common/lib/xmodule/xmodule/tests/test_sequence.py +++ b/common/lib/xmodule/xmodule/tests/test_sequence.py @@ -5,7 +5,6 @@ Tests for sequence module. import json from datetime import timedelta -import ddt from django.utils.timezone import now from freezegun import freeze_time from mock import Mock, patch @@ -13,7 +12,7 @@ from xmodule.seq_module import SequenceModule from xmodule.tests import get_test_system from xmodule.tests.helpers import StubUserService from xmodule.tests.xml import factories as xml, XModuleXmlImportTest -from xmodule.x_module import PUBLIC_VIEW, STUDENT_VIEW +from xmodule.x_module import STUDENT_VIEW TODAY = now() DUE_DATE = TODAY + timedelta(days=7) @@ -21,7 +20,6 @@ PAST_DUE_BEFORE_END_DATE = TODAY + timedelta(days=14) COURSE_END_DATE = TODAY + timedelta(days=21) -@ddt.ddt class SequenceBlockTestCase(XModuleXmlImportTest): """ Base class for tests of Sequence Module. @@ -94,12 +92,7 @@ class SequenceBlockTestCase(XModuleXmlImportTest): module_system.descriptor_runtime = block._runtime # pylint: disable=protected-access block.xmodule_runtime = module_system - def _get_rendered_view(self, - sequence, - requested_child=None, - extra_context=None, - self_paced=False, - view=STUDENT_VIEW): + def _get_rendered_student_view(self, sequence, requested_child=None, extra_context=None, self_paced=False): """ Returns the rendered student view for the given sequence and the requested_child parameter. @@ -113,7 +106,7 @@ class SequenceBlockTestCase(XModuleXmlImportTest): with patch.object(SequenceModule, '_get_course') as mock_course: self.course.self_paced = self_paced mock_course.return_value = self.course - return sequence.xmodule_runtime.render(sequence, view, context).content + return sequence.xmodule_runtime.render(sequence, STUDENT_VIEW, context).content def _assert_view_at_position(self, rendered_html, expected_position): """ @@ -125,16 +118,10 @@ class SequenceBlockTestCase(XModuleXmlImportTest): seq_module = SequenceModule(runtime=Mock(position=2), descriptor=Mock(), scope_ids=Mock()) self.assertEquals(seq_module.position, 2) # matches position set in the runtime - @ddt.unpack - @ddt.data( - {'view': STUDENT_VIEW}, - {'view': PUBLIC_VIEW}, - ) - def test_render_student_view(self, view): - html = self._get_rendered_view( + def test_render_student_view(self): + html = self._get_rendered_student_view( self.sequence_3_1, extra_context=dict(next_url='NextSequential', prev_url='PrevSequential'), - view=view ) self._assert_view_at_position(html, expected_position=1) self.assertIn(unicode(self.sequence_3_1.location), html) @@ -143,40 +130,28 @@ class SequenceBlockTestCase(XModuleXmlImportTest): self.assertIn("'prev_url': 'PrevSequential'", html) self.assertNotIn("fa fa-check-circle check-circle is-hidden", html) - @ddt.unpack - @ddt.data( - {'view': STUDENT_VIEW}, - {'view': PUBLIC_VIEW}, - ) - def test_student_view_first_child(self, view): - html = self._get_rendered_view( - self.sequence_3_1, requested_child='first', view=view - ) + def test_student_view_first_child(self): + html = self._get_rendered_student_view(self.sequence_3_1, requested_child='first') self._assert_view_at_position(html, expected_position=1) - @ddt.unpack - @ddt.data( - {'view': STUDENT_VIEW}, - {'view': PUBLIC_VIEW}, - ) - def test_student_view_last_child(self, view): - html = self._get_rendered_view(self.sequence_3_1, requested_child='last', view=view) + def test_student_view_last_child(self): + html = self._get_rendered_student_view(self.sequence_3_1, requested_child='last') self._assert_view_at_position(html, expected_position=3) def test_tooltip(self): - html = self._get_rendered_view(self.sequence_3_1, requested_child=None) + html = self._get_rendered_student_view(self.sequence_3_1, requested_child=None) for child in self.sequence_3_1.children: self.assertIn("'page_title': '{}'".format(child.block_id), html) def test_hidden_content_before_due(self): - html = self._get_rendered_view(self.sequence_4_1) + html = self._get_rendered_student_view(self.sequence_4_1) self.assertIn("seq_module.html", html) self.assertIn("'banner_text': None", html) def test_hidden_content_past_due(self): with freeze_time(COURSE_END_DATE): progress_url = 'http://test_progress_link' - html = self._get_rendered_view( + html = self._get_rendered_student_view( self.sequence_4_1, extra_context=dict(progress_url=progress_url), ) @@ -185,7 +160,7 @@ class SequenceBlockTestCase(XModuleXmlImportTest): def test_masquerade_hidden_content_past_due(self): with freeze_time(COURSE_END_DATE): - html = self._get_rendered_view( + html = self._get_rendered_student_view( self.sequence_4_1, extra_context=dict(specific_masquerade=True), ) @@ -198,14 +173,14 @@ class SequenceBlockTestCase(XModuleXmlImportTest): def test_hidden_content_self_paced_past_due_before_end(self): with freeze_time(PAST_DUE_BEFORE_END_DATE): - html = self._get_rendered_view(self.sequence_4_1, self_paced=True) + html = self._get_rendered_student_view(self.sequence_4_1, self_paced=True) self.assertIn("seq_module.html", html) self.assertIn("'banner_text': None", html) def test_hidden_content_self_paced_past_end(self): with freeze_time(COURSE_END_DATE + timedelta(days=7)): progress_url = 'http://test_progress_link' - html = self._get_rendered_view( + html = self._get_rendered_student_view( self.sequence_4_1, extra_context=dict(progress_url=progress_url), self_paced=True, @@ -273,7 +248,7 @@ class SequenceBlockTestCase(XModuleXmlImportTest): self.sequence_1_2.xmodule_runtime._services['gating'] = gating_mock_1_2 # pylint: disable=protected-access self.sequence_1_2.display_name = 'sequence_1_2' - html = self._get_rendered_view( + html = self._get_rendered_student_view( self.sequence_1_2, extra_context=dict(next_url='NextSequential', prev_url='PrevSequential'), ) @@ -286,7 +261,7 @@ class SequenceBlockTestCase(XModuleXmlImportTest): gating_mock_1_2.return_value.required_prereq.return_value = True gating_mock_1_2.return_value.compute_is_prereq_met.return_value = [True, {}] - html = self._get_rendered_view( + html = self._get_rendered_student_view( self.sequence_1_2, extra_context=dict(next_url='NextSequential', prev_url='PrevSequential'), ) @@ -299,7 +274,7 @@ class SequenceBlockTestCase(XModuleXmlImportTest): gating_mock_1_2.return_value.required_prereq.return_value = True gating_mock_1_2.return_value.compute_is_prereq_met.return_value = [True, {}] - html = self._get_rendered_view( + html = self._get_rendered_student_view( self.sequence_1_2, extra_context=dict(next_url='NextSequential', prev_url='PrevSequential'), ) diff --git a/common/lib/xmodule/xmodule/tests/test_vertical.py b/common/lib/xmodule/xmodule/tests/test_vertical.py index 69d5660fe6..c272b95be3 100644 --- a/common/lib/xmodule/xmodule/tests/test_vertical.py +++ b/common/lib/xmodule/xmodule/tests/test_vertical.py @@ -11,12 +11,14 @@ import json import ddt from fs.memoryfs import MemoryFS from mock import Mock, patch +import six from . import get_test_system from .helpers import StubUserService from .xml import XModuleXmlImportTest from .xml import factories as xml -from ..x_module import STUDENT_VIEW, AUTHOR_VIEW, PUBLIC_VIEW +from ..x_module import STUDENT_VIEW, AUTHOR_VIEW + COMPLETION_DELAY = 9876 @@ -109,41 +111,34 @@ class VerticalBlockTestCase(BaseVerticalBlockTest): """ shard = 1 - def assert_bookmark_info(self, assertion, content): + def assert_bookmark_info_in(self, content): """ - Assert content has/hasn't all the bookmark info. + Assert content has all the bookmark info. """ - assertion('bookmark_id', content) - assertion('{},{}'.format(self.username, unicode(self.vertical.location)), content) - assertion('bookmarked', content) - assertion('show_bookmark_button', content) + self.assertIn('bookmark_id', content) + self.assertIn('{},{}'.format(self.username, unicode(self.vertical.location)), content) + self.assertIn('bookmarked', content) + self.assertIn('show_bookmark_button', content) @ddt.unpack @ddt.data( - {'context': None, 'view': STUDENT_VIEW}, - {'context': {}, 'view': STUDENT_VIEW}, - {'context': {}, 'view': PUBLIC_VIEW}, + {'context': None}, + {'context': {}} ) - def test_render_student_preview_view(self, context, view): + def test_render_student_view(self, context): """ - Test the rendering of the student and public view. + Test the rendering of the student view. """ self.module_system._services['bookmarks'] = Mock() - if view == STUDENT_VIEW: - self.module_system._services['user'] = StubUserService() - self.module_system._services['completion'] = StubCompletionService(enabled=True, completion_value=0.0) - elif view == PUBLIC_VIEW: - self.module_system._services['user'] = StubUserService(is_anonymous=True) + self.module_system._services['user'] = StubUserService() + self.module_system._services['completion'] = StubCompletionService(enabled=True, completion_value=0.0) html = self.module_system.render( - self.vertical, view, self.default_context if context is None else context + self.vertical, STUDENT_VIEW, self.default_context if context is None else context ).content self.assertIn(self.test_html_1, html) self.assertIn(self.test_html_2, html) - if view == STUDENT_VIEW: - self.assert_bookmark_info(self.assertIn, html) - else: - self.assert_bookmark_info(self.assertNotIn, html) + self.assert_bookmark_info_in(html) @ddt.unpack @ddt.data( diff --git a/common/lib/xmodule/xmodule/vertical_block.py b/common/lib/xmodule/xmodule/vertical_block.py index 0d646337a6..ec6d2100fc 100644 --- a/common/lib/xmodule/xmodule/vertical_block.py +++ b/common/lib/xmodule/xmodule/vertical_block.py @@ -17,7 +17,7 @@ from xmodule.progress import Progress from xmodule.seq_module import SequenceFields from xmodule.studio_editable import StudioEditableBlock from xmodule.util.xmodule_django import add_webpack_to_fragment -from xmodule.x_module import STUDENT_VIEW, PUBLIC_VIEW, XModuleFields +from xmodule.x_module import STUDENT_VIEW, XModuleFields from xmodule.xml_module import XmlParserMixin log = logging.getLogger(__name__) @@ -43,9 +43,9 @@ class VerticalBlock(SequenceFields, XModuleFields, StudioEditableBlock, XmlParse show_in_read_only_mode = True - def _student_or_public_view(self, context, view): + def student_view(self, context): """ - Renders the requested view type of the block in the LMS. + Renders the student view of the block in the LMS. """ fragment = Fragment() contents = [] @@ -55,14 +55,12 @@ class VerticalBlock(SequenceFields, XModuleFields, StudioEditableBlock, XmlParse else: child_context = {} - if view == STUDENT_VIEW: - if 'bookmarked' not in child_context: - bookmarks_service = self.runtime.service(self, 'bookmarks') - child_context['bookmarked'] = bookmarks_service.is_bookmarked( - usage_key=self.location), # pylint: disable=no-member - if 'username' not in child_context: - user_service = self.runtime.service(self, 'user') - child_context['username'] = user_service.get_current_user().opt_attrs['edx-platform.username'] + if 'bookmarked' not in child_context: + bookmarks_service = self.runtime.service(self, 'bookmarks') + child_context['bookmarked'] = bookmarks_service.is_bookmarked(usage_key=self.location), # pylint: disable=no-member + if 'username' not in child_context: + user_service = self.runtime.service(self, 'user') + child_context['username'] = user_service.get_current_user().opt_attrs['edx-platform.username'] child_blocks = self.get_display_items() @@ -82,7 +80,7 @@ class VerticalBlock(SequenceFields, XModuleFields, StudioEditableBlock, XmlParse child_block_context['wrap_xblock_data'] = { 'mark-completed-on-view-after-delay': complete_on_view_delay } - rendered_child = child.render(view, child_block_context) + rendered_child = child.render(STUDENT_VIEW, child_block_context) fragment.add_fragment_resources(rendered_child) contents.append({ @@ -90,39 +88,20 @@ class VerticalBlock(SequenceFields, XModuleFields, StudioEditableBlock, XmlParse 'content': rendered_child.content }) - fragment_context = { + fragment.add_content(self.system.render_template('vert_module.html', { 'items': contents, 'xblock_context': context, 'unit_title': self.display_name_with_default if not is_child_of_vertical else None, - } - - if view == STUDENT_VIEW: - fragment_context.update({ - 'show_bookmark_button': child_context.get('show_bookmark_button', not is_child_of_vertical), - 'bookmarked': child_context['bookmarked'], - 'bookmark_id': u"{},{}".format( - child_context['username'], unicode(self.location)), # pylint: disable=no-member - }) - - fragment.add_content(self.system.render_template('vert_module.html', fragment_context)) + 'show_bookmark_button': child_context.get('show_bookmark_button', not is_child_of_vertical), + 'bookmarked': child_context['bookmarked'], + 'bookmark_id': u"{},{}".format(child_context['username'], unicode(self.location)), # pylint: disable=no-member + })) add_webpack_to_fragment(fragment, 'VerticalStudentView') fragment.initialize_js('VerticalStudentView') return fragment - def student_view(self, context): - """ - Renders the student view of the block in the LMS. - """ - return self._student_or_public_view(context, STUDENT_VIEW) - - def public_view(self, context): - """ - Renders the anonymous view of the block in the LMS. - """ - return self._student_or_public_view(context, PUBLIC_VIEW) - def author_view(self, context): """ Renders the Studio preview view, which supports drag and drop. diff --git a/common/lib/xmodule/xmodule/x_module.py b/common/lib/xmodule/xmodule/x_module.py index 52a208edd3..29f94d7bbb 100644 --- a/common/lib/xmodule/xmodule/x_module.py +++ b/common/lib/xmodule/xmodule/x_module.py @@ -39,8 +39,6 @@ from opaque_keys.edx.asides import AsideUsageKeyV2, AsideDefinitionKeyV2 from xmodule.exceptions import UndefinedContext import dogstats_wrapper as dog_stats_api -from openedx.core.djangolib.markup import HTML - log = logging.getLogger(__name__) XMODULE_METRIC_NAME = 'edxapp.xmodule' @@ -57,10 +55,6 @@ DEPRECATION_VSCOMPAT_EVENT = 'deprecation.vscompat' # the XBlock also implements author_view. STUDENT_VIEW = 'student_view' -# This is the view that will be rendered to display the XBlock in the LMS for unenrolled learners. -# Implementations of this view should assume that a user and user data are not available. -PUBLIC_VIEW = 'public_view' - # An optional view of the XBlock similar to student_view, but with possible inline # editing capabilities. This view differs from studio_view in that it should be as similar to student_view # as possible. When previewing XBlocks within Studio, Studio will prefer author_view to student_view. @@ -71,9 +65,8 @@ AUTHOR_VIEW = 'author_view' 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] +PREVIEW_VIEWS = [STUDENT_VIEW, AUTHOR_VIEW] -DEFAULT_PUBLIC_VIEW_MESSAGE = u'Please enroll to view this content.' # 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,17 +759,6 @@ class XModuleMixin(XModuleFields, XBlock): return metadata_field_editor_info - def public_view(self, _context): - """ - Default message for blocks that don't implement public_view - """ - alert_html = HTML( - u'' - ) - return Fragment(alert_html.format(DEFAULT_PUBLIC_VIEW_MESSAGE)) - class ProxyAttribute(object): """ @@ -1241,7 +1223,6 @@ class XModuleDescriptor(HTMLSnippet, ResourceTemplates, XModuleMixin): get_score = module_attr('get_score') handle_ajax = module_attr('handle_ajax') student_view = module_attr(STUDENT_VIEW) - public_view = module_attr(PUBLIC_VIEW) get_child_descriptors = module_attr('get_child_descriptors') xmodule_handler = module_attr('xmodule_handler') diff --git a/lms/djangoapps/course_api/blocks/transformers/milestones.py b/lms/djangoapps/course_api/blocks/transformers/milestones.py index 3d88a6d27b..6f585acc46 100644 --- a/lms/djangoapps/course_api/blocks/transformers/milestones.py +++ b/lms/djangoapps/course_api/blocks/transformers/milestones.py @@ -8,7 +8,6 @@ from django.conf import settings from edx_proctoring.api import get_attempt_status_summary from edx_proctoring.exceptions import ProctoredExamNotFoundException -from student.models import CourseEnrollment from openedx.core.djangoapps.content.block_structure.transformer import ( BlockStructureTransformer, ) @@ -145,11 +144,7 @@ class MilestonesAndSpecialExamsTransformer(BlockStructureTransformer): """ course_key = block_structure.root_block_usage_key.course_key - user_can_skip_entrance_exam = False - is_enrolled = CourseEnrollment.is_enrolled(usage_info.user, course_key) - if is_enrolled: - user_can_skip_entrance_exam = EntranceExamConfiguration.user_can_skip_entrance_exam( - usage_info.user, course_key) + user_can_skip_entrance_exam = EntranceExamConfiguration.user_can_skip_entrance_exam(usage_info.user, course_key) required_content = milestones_helpers.get_required_content(course_key, usage_info.user) if not required_content: diff --git a/lms/djangoapps/course_api/blocks/transformers/tests/test_milestones.py b/lms/djangoapps/course_api/blocks/transformers/tests/test_milestones.py index 9947d4978a..115e69fcde 100644 --- a/lms/djangoapps/course_api/blocks/transformers/tests/test_milestones.py +++ b/lms/djangoapps/course_api/blocks/transformers/tests/test_milestones.py @@ -162,7 +162,7 @@ class MilestonesTransformerTestCase(CourseStructureTestCase, MilestonesTestCaseM self.course.enable_subsection_gating = True self.setup_gated_section(self.blocks[gated_block_ref], self.blocks[gating_block_ref]) - with self.assertNumQueries(7): + with self.assertNumQueries(6): self.get_blocks_and_check_against_expected(self.user, expected_blocks_before_completion) # clear the request cache to simulate a new request @@ -175,7 +175,7 @@ class MilestonesTransformerTestCase(CourseStructureTestCase, MilestonesTestCaseM self.user, ) - with self.assertNumQueries(7): + with self.assertNumQueries(6): self.get_blocks_and_check_against_expected(self.user, self.ALL_BLOCKS_EXCEPT_SPECIAL) def test_staff_access(self): diff --git a/lms/djangoapps/courseware/tests/test_views.py b/lms/djangoapps/courseware/tests/test_views.py index c3e905fb3b..383ca77b79 100644 --- a/lms/djangoapps/courseware/tests/test_views.py +++ b/lms/djangoapps/courseware/tests/test_views.py @@ -59,24 +59,20 @@ from openedx.core.djangoapps.content.course_overviews.models import CourseOvervi from openedx.core.djangoapps.crawlers.models import CrawlersConfig from openedx.core.djangoapps.credit.api import set_credit_requirements from openedx.core.djangoapps.credit.models import CreditCourse, CreditProvider +from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag, WaffleFlagNamespace from openedx.core.djangoapps.waffle_utils.testutils import WAFFLE_TABLES, override_waffle_flag from openedx.core.djangolib.testing.utils import get_mock_request from openedx.core.lib.gating import api as gating_api from openedx.core.lib.tests import attr from openedx.core.lib.url_utils import quote_slashes from openedx.features.course_duration_limits.config import CONTENT_TYPE_GATING_FLAG -from openedx.features.course_experience import ( - COURSE_ENABLE_UNENROLLED_ACCESS_FLAG, - COURSE_OUTLINE_PAGE_FLAG, - UNIFIED_COURSE_TAB_FLAG, -) +from openedx.features.course_experience import COURSE_OUTLINE_PAGE_FLAG, UNIFIED_COURSE_TAB_FLAG from openedx.features.enterprise_support.tests.mixins.enterprise import EnterpriseTestConsentRequired from student.models import CourseEnrollment from student.tests.factories import TEST_PASSWORD, AdminFactory, CourseEnrollmentFactory, UserFactory from util.tests.test_date_utils import fake_pgettext, fake_ugettext from util.url import reload_django_url_config from util.views import ensure_valid_course_key -from xmodule.course_module import COURSE_VISIBILITY_PRIVATE, COURSE_VISIBILITY_PUBLIC_OUTLINE, COURSE_VISIBILITY_PUBLIC from xmodule.graders import ShowCorrectness from xmodule.modulestore import ModuleStoreEnum from xmodule.modulestore.django import modulestore @@ -2332,6 +2328,7 @@ class TestIndexView(ModuleStoreTestCase): """ Tests of the courseware.views.index view. """ + SEO_WAFFLE_FLAG = CourseWaffleFlag(WaffleFlagNamespace(name='seo'), 'enable_anonymous_courseware_access') @XBlock.register_temp_plugin(ViewCheckerBlock, 'view_checker') @ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split) @@ -2401,23 +2398,12 @@ class TestIndexView(ModuleStoreTestCase): ) self.assertIn("Activate Block ID: test_block_id", response.content) - @ddt.data( - [False, COURSE_VISIBILITY_PRIVATE, 302], - [False, COURSE_VISIBILITY_PUBLIC_OUTLINE, 302], - [False, COURSE_VISIBILITY_PUBLIC, 302], - [True, COURSE_VISIBILITY_PRIVATE, 302], - [True, COURSE_VISIBILITY_PUBLIC_OUTLINE, 302], - [True, COURSE_VISIBILITY_PUBLIC, 200], - ) - @ddt.unpack - def test_unenrolled_access(self, waffle_override, course_visibility, expected_status): - course = CourseFactory(course_visibility=course_visibility) + def test_anonymous_access(self): + course = CourseFactory() with self.store.bulk_operations(course.id): chapter = ItemFactory(parent=course, category='chapter') section = ItemFactory(parent=chapter, category='sequential') - vertical = ItemFactory.create(parent=section, category='vertical', display_name="Vertical") - ItemFactory.create(parent=vertical, category='html', display_name='HTML block') - ItemFactory.create(parent=vertical, category='video', display_name='Video') + ItemFactory.create(parent=section, category='vertical', display_name="Vertical") url = reverse( 'courseware_section', @@ -2427,29 +2413,23 @@ class TestIndexView(ModuleStoreTestCase): 'section': section.url_name, } ) + response = self.client.get(url, follow=False) + assert response.status_code == 302 - with override_waffle_flag(COURSE_ENABLE_UNENROLLED_ACCESS_FLAG, active=waffle_override): - response = self.client.get(url, follow=False) - assert response.status_code == expected_status - if expected_status == 200: # Public access is available - self.assertIn('data-save-position="false"', response.content) - self.assertIn('data-show-completion="false"', response.content) - self.assertIn('xblock-public_view-sequential', response.content) - self.assertIn('xblock-public_view-vertical', response.content) - self.assertIn('xblock-public_view-html', response.content) - self.assertIn('xblock-public_view-video', response.content) - - user = UserFactory() - self.assertTrue(self.client.login(username=user.username, password='test')) - response = self.client.get(url, follow=False) - # Logged in but unenrolled learners get the same behaviour as anonymous learners. - assert response.status_code == expected_status - - CourseEnrollmentFactory(user=user, course_id=course.id) + waffle_flag = CourseWaffleFlag(WaffleFlagNamespace(name='seo'), 'enable_anonymous_courseware_access') + with override_waffle_flag(waffle_flag, active=True): response = self.client.get(url, follow=False) assert response.status_code == 200 - self.assertIn('data-save-position="true"', response.content) - self.assertIn('data-show-completion="true"', response.content) + self.assertIn('data-save-position="false"', response.content) + self.assertIn('data-show-completion="false"', response.content) + + user = UserFactory() + CourseEnrollmentFactory(user=user, course_id=course.id) + self.assertTrue(self.client.login(username=user.username, password='test')) + response = self.client.get(url, follow=False) + assert response.status_code == 200 + self.assertIn('data-save-position="true"', response.content) + self.assertIn('data-show-completion="true"', response.content) @attr(shard=5) diff --git a/lms/djangoapps/courseware/views/index.py b/lms/djangoapps/courseware/views/index.py index 304db62b49..e36cb84748 100644 --- a/lms/djangoapps/courseware/views/index.py +++ b/lms/djangoapps/courseware/views/index.py @@ -24,8 +24,6 @@ from opaque_keys.edx.keys import CourseKey from web_fragments.fragment import Fragment from edxmako.shortcuts import render_to_response, render_to_string - -from student.models import CourseEnrollment from lms.djangoapps.courseware.exceptions import CourseAccessRedirect from lms.djangoapps.experiments.utils import get_experiment_user_metadata_context from lms.djangoapps.gating.api import get_entrance_exam_score_ratio, get_entrance_exam_usage_key @@ -34,20 +32,17 @@ from openedx.core.djangoapps.crawlers.models import CrawlersConfig from openedx.core.djangoapps.lang_pref import LANGUAGE_KEY from openedx.core.djangoapps.user_api.preferences.api import get_user_preference from openedx.core.djangoapps.util.user_messages import PageLevelMessages -from openedx.core.djangoapps.waffle_utils import WaffleSwitchNamespace +from openedx.core.djangoapps.waffle_utils import WaffleSwitchNamespace, WaffleFlagNamespace, CourseWaffleFlag from openedx.core.djangolib.markup import HTML, Text from openedx.features.course_duration_limits.access import register_course_expired_message -from openedx.features.course_experience import ( - COURSE_OUTLINE_PAGE_FLAG, default_course_url_name, COURSE_ENABLE_UNENROLLED_ACCESS_FLAG -) +from openedx.features.course_experience import COURSE_OUTLINE_PAGE_FLAG, default_course_url_name from openedx.features.course_experience.views.course_sock import CourseSockFragmentView from openedx.features.enterprise_support.api import data_sharing_consent_required from shoppingcart.models import CourseRegistrationCode from student.views import is_course_blocked from util.views import ensure_valid_course_key from xmodule.modulestore.django import modulestore -from xmodule.course_module import COURSE_VISIBILITY_PUBLIC -from xmodule.x_module import PUBLIC_VIEW, STUDENT_VIEW +from xmodule.x_module import STUDENT_VIEW from .views import CourseTabView from ..access import has_access from ..access_utils import check_course_open_for_learner @@ -74,8 +69,9 @@ class CoursewareIndex(View): """ @cached_property - def enable_unenrolled_access(self): - return COURSE_ENABLE_UNENROLLED_ACCESS_FLAG.is_enabled(self.course_key) + def enable_anonymous_courseware_access(self): + waffle_flag = CourseWaffleFlag(WaffleFlagNamespace(name='seo'), 'enable_anonymous_courseware_access') + return waffle_flag.is_enabled(self.course_key) @method_decorator(ensure_csrf_cookie) @method_decorator(cache_control(no_cache=True, no_store=True, must_revalidate=True)) @@ -102,7 +98,7 @@ class CoursewareIndex(View): """ self.course_key = CourseKey.from_string(course_id) - if not (request.user.is_authenticated or self.enable_unenrolled_access): + if not (request.user.is_authenticated or self.enable_anonymous_courseware_access): return redirect_to_login(request.get_full_path()) self.original_chapter_url_name = chapter @@ -121,16 +117,8 @@ class CoursewareIndex(View): self.course = get_course_with_access( request.user, 'load', self.course_key, depth=CONTENT_DEPTH, - check_if_enrolled=False, + check_if_enrolled=not self.enable_anonymous_courseware_access, ) - is_enrolled = CourseEnrollment.is_enrolled(request.user, self.course_key) - if is_enrolled: - self.view = STUDENT_VIEW - elif self.enable_unenrolled_access and self.course.course_visibility == COURSE_VISIBILITY_PUBLIC: - self.view = PUBLIC_VIEW - else: - raise CourseAccessRedirect(reverse('about_course', args=[unicode(self.course_key)])) - self.is_staff = has_access(request.user, 'staff', self.course) self._setup_masquerade_for_effective_user() register_course_expired_message(request, self.course) @@ -450,9 +438,7 @@ class CoursewareIndex(View): table_of_contents['previous_of_active_section'], table_of_contents['next_of_active_section'], ) - - courseware_context['fragment'] = self.section.render(self.view, section_context) - + courseware_context['fragment'] = self.section.render(STUDENT_VIEW, section_context) if self.section.position and self.section.has_children: self._add_sequence_title_to_context(courseware_context) diff --git a/lms/static/sass/course/courseware/_courseware.scss b/lms/static/sass/course/courseware/_courseware.scss index 31064a7962..f3aa08361f 100644 --- a/lms/static/sass/course/courseware/_courseware.scss +++ b/lms/static/sass/course/courseware/_courseware.scss @@ -139,8 +139,7 @@ html.video-fullscreen { word-break: break-word; margin: 0 auto; - &.xblock-student_view-vertical, - &.xblock-public_view-vertical { + &.xblock-student_view-vertical { max-width: $text-width-readability-max; } } @@ -582,8 +581,7 @@ html.video-fullscreen { padding: 0 0 15px; } - .vert > .xblock-student_view.is-hidden, - .vert > .xblock-public_view.is-hidden { + .vert > .xblock-student_view.is-hidden { display: none; border-bottom: 0; margin-bottom: 0; @@ -596,8 +594,7 @@ html.video-fullscreen { } } - section.xblock-student_view-wrapper div.vert-mod > div, - section.xblock-public_view-wrapper div.vert-mod > div { + section.xblock-student_view-wrapper div.vert-mod > div { border-bottom: none; } diff --git a/openedx/features/course_experience/__init__.py b/openedx/features/course_experience/__init__.py index 807bc6153f..7741d2bc12 100644 --- a/openedx/features/course_experience/__init__.py +++ b/openedx/features/course_experience/__init__.py @@ -43,10 +43,6 @@ LATEST_UPDATE_FLAG = CourseWaffleFlag(WAFFLE_FLAG_NAMESPACE, 'latest_update') # Waffle flag to enable the use of Bootstrap for course experience pages USE_BOOTSTRAP_FLAG = CourseWaffleFlag(WAFFLE_FLAG_NAMESPACE, 'use_bootstrap', flag_undefined_default=True) -# Waffle flag to enable anonymous access to a course -SEO_WAFFLE_FLAG_NAMESPACE = WaffleFlagNamespace(name='seo') -COURSE_ENABLE_UNENROLLED_ACCESS_FLAG = CourseWaffleFlag(SEO_WAFFLE_FLAG_NAMESPACE, 'enable_anonymous_courseware_access') - def course_home_page_title(course): # pylint: disable=unused-argument """ diff --git a/openedx/features/course_experience/templates/course_experience/course-outline-fragment.html b/openedx/features/course_experience/templates/course_experience/course-outline-fragment.html index 5fc3f678b4..5f89fd2e9c 100644 --- a/openedx/features/course_experience/templates/course_experience/course-outline-fragment.html +++ b/openedx/features/course_experience/templates/course_experience/course-outline-fragment.html @@ -51,7 +51,7 @@ course_sections = blocks.get('children') completed_prereqs = gated_content[subsection['id']]['completed_prereqs'] if gated_subsection else False subsection_is_auto_opened = subsection.get('resume_block') is True %> -