diff --git a/common/djangoapps/django_comment_common/models.py b/common/djangoapps/django_comment_common/models.py index 6f753d1465..5db7545d43 100644 --- a/common/djangoapps/django_comment_common/models.py +++ b/common/djangoapps/django_comment_common/models.py @@ -135,6 +135,9 @@ def permission_blacked_out(course, role_names, permission_name): def all_permissions_for_user_in_course(user, course_id): # pylint: disable=invalid-name """Returns all the permissions the user has in the given course.""" + if not user.is_authenticated(): + return {} + course = modulestore().get_course(course_id) if course is None: raise ItemNotFoundError(course_id) diff --git a/common/djangoapps/util/milestones_helpers.py b/common/djangoapps/util/milestones_helpers.py index 257e16e477..fe2b6e2547 100644 --- a/common/djangoapps/util/milestones_helpers.py +++ b/common/djangoapps/util/milestones_helpers.py @@ -5,7 +5,7 @@ Utility library for working with the edx-milestones app from django.conf import settings from django.utils.translation import ugettext as _ from milestones import api as milestones_api -from milestones.exceptions import InvalidMilestoneRelationshipTypeException +from milestones.exceptions import InvalidMilestoneRelationshipTypeException, InvalidUserException from milestones.models import MilestoneRelationshipType from milestones.services import MilestonesService from opaque_keys import InvalidKeyError @@ -213,21 +213,32 @@ def get_required_content(course_key, user): """ required_content = [] if settings.FEATURES.get('MILESTONES_APP'): - # Get all of the outstanding milestones for this course, for this user - try: - milestone_paths = get_course_milestones_fulfillment_paths( - unicode(course_key), - serialize_user(user) - ) - except InvalidMilestoneRelationshipTypeException: - return required_content + course_run_id = unicode(course_key) + + if user.is_authenticated(): + # Get all of the outstanding milestones for this course, for this user + try: + + milestone_paths = get_course_milestones_fulfillment_paths( + course_run_id, + serialize_user(user) + ) + except InvalidMilestoneRelationshipTypeException: + return required_content + + # For each outstanding milestone, see if this content is one of its fulfillment paths + for path_key in milestone_paths: + milestone_path = milestone_paths[path_key] + if milestone_path.get('content') and len(milestone_path['content']): + for content in milestone_path['content']: + required_content.append(content) + else: + if get_course_milestones(course_run_id): + # NOTE (CCB): The initial version of anonymous courseware access is very simple. We avoid accidentally + # exposing locked content by simply avoiding anonymous access altogether for courses runs with + # milestones. + raise InvalidUserException('Anonymous access is not allowed for course runs with milestones set.') - # For each outstanding milestone, see if this content is one of its fulfillment paths - for path_key in milestone_paths: - milestone_path = milestone_paths[path_key] - if milestone_path.get('content') and len(milestone_path['content']): - for content in milestone_path['content']: - required_content.append(content) return required_content diff --git a/common/djangoapps/util/tests/test_milestones_helpers.py b/common/djangoapps/util/tests/test_milestones_helpers.py index 163c7d2d14..e1ac1110a2 100644 --- a/common/djangoapps/util/tests/test_milestones_helpers.py +++ b/common/djangoapps/util/tests/test_milestones_helpers.py @@ -3,15 +3,17 @@ Tests for the milestones helpers library, which is the integration point for the """ import ddt +import pytest from django.conf import settings +from django.contrib.auth.models import AnonymousUser +from milestones import api as milestones_api from milestones.exceptions import InvalidCourseKeyException, InvalidUserException +from milestones.models import MilestoneRelationshipType from mock import patch -from milestones import api as milestones_api -from milestones.models import MilestoneRelationshipType +from util import milestones_helpers from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory -from util import milestones_helpers @patch.dict(settings.FEATURES, {'MILESTONES_APP': False}) @@ -134,3 +136,20 @@ class MilestonesHelpersTestCase(ModuleStoreTestCase): milestones_helpers.any_unfulfilled_milestones(None, self.user['id']) with self.assertRaises(InvalidUserException): milestones_helpers.any_unfulfilled_milestones(self.course.id, None) + + @patch.dict(settings.FEATURES, {'MILESTONES_APP': True}) + def test_get_required_content_with_anonymous_user(self): + course = CourseFactory() + + required_content = milestones_helpers.get_required_content(course.id, AnonymousUser()) + assert required_content == [] + + # NOTE (CCB): The initial version of anonymous courseware access is very simple. We avoid accidentally + # exposing locked content by simply avoiding anonymous access altogether for courses runs with milestones. + milestone = milestones_api.add_milestone({ + 'name': 'test', + 'namespace': 'test', + }) + milestones_helpers.add_course_milestone(str(course.id), 'requires', milestone) + with pytest.raises(InvalidUserException): + milestones_helpers.get_required_content(course.id, AnonymousUser()) diff --git a/common/lib/xmodule/xmodule/seq_module.py b/common/lib/xmodule/xmodule/seq_module.py index 8a132cda0c..567a6bedb0 100644 --- a/common/lib/xmodule/xmodule/seq_module.py +++ b/common/lib/xmodule/xmodule/seq_module.py @@ -4,22 +4,22 @@ xModule implementation of a learning sequence # pylint: disable=abstract-method import collections -from datetime import datetime import json import logging -from pkg_resources import resource_string -from pytz import UTC +from datetime import datetime from lxml import etree +from pkg_resources import resource_string +from pytz import UTC from xblock.core import XBlock -from xblock.fields import Integer, Scope, Boolean, String, List +from xblock.fields import Boolean, Integer, List, Scope, String from xblock.fragment import Fragment from .exceptions import NotFoundError from .fields import Date from .mako_module import MakoModuleDescriptor from .progress import Progress -from .x_module import XModule, STUDENT_VIEW +from .x_module import STUDENT_VIEW, XModule from .xml_module import XmlDescriptor log = logging.getLogger(__name__) @@ -292,6 +292,10 @@ class SequenceModule(SequenceFields, ProctoringFields, XModule): self.verify_current_content_visibility(hidden_date, self.hide_after_due) ) + def is_user_authenticated(self, context): + # 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_view(self, context, banner_text=None): """ Returns the rendered student view of the content of this @@ -312,6 +316,7 @@ class SequenceModule(SequenceFields, ProctoringFields, XModule): 'next_url': context.get('next_url'), 'prev_url': context.get('prev_url'), 'banner_text': banner_text, + 'disable_navigation': not self.is_user_authenticated(context), } fragment.add_content(self.system.render_template("seq_module.html", params)) @@ -325,6 +330,11 @@ class SequenceModule(SequenceFields, ProctoringFields, XModule): Update the user's sequential position given the context and the number_of_display_items """ + + position = context.get('position') + if position: + self.position = position + # If we're rendering this sequence, but no position is set yet, # or exceeds the length of the displayable items, # default the position to the first element @@ -341,16 +351,36 @@ class SequenceModule(SequenceFields, ProctoringFields, XModule): display_items. Returns a list of dict objects with information about the given display_items. """ - bookmarks_service = self.runtime.service(self, "bookmarks") - context["username"] = self.runtime.service(self, "user").get_current_user().opt_attrs['edx-platform.username'] + is_user_authenticated = self.is_user_authenticated(context) + bookmarks_service = self.runtime.service(self, 'bookmarks') + context['username'] = self.runtime.service(self, 'user').get_current_user().opt_attrs.get( + 'edx-platform.username') display_names = [ self.get_parent().display_name_with_default, self.display_name_with_default ] contents = [] for item in display_items: - is_bookmarked = bookmarks_service.is_bookmarked(usage_key=item.scope_ids.usage_id) - context["bookmarked"] = is_bookmarked + # NOTE (CCB): This seems like a hack, but I don't see a better method of determining the type/category. + item_type = item.get_icon_class() + usage_id = item.scope_ids.usage_id + + if item_type == 'problem' and not is_user_authenticated: + log.info( + 'Problem [%s] was not rendered because anonymous access is not allowed for graded content', + usage_id + ) + continue + + show_bookmark_button = False + is_bookmarked = False + + if is_user_authenticated: + show_bookmark_button = True + is_bookmarked = bookmarks_service.is_bookmarked(usage_key=usage_id) + + context['show_bookmark_button'] = show_bookmark_button + context['bookmarked'] = is_bookmarked rendered_item = item.render(STUDENT_VIEW, context) fragment.add_frag_resources(rendered_item) @@ -358,8 +388,8 @@ class SequenceModule(SequenceFields, ProctoringFields, XModule): iteminfo = { 'content': rendered_item.content, 'page_title': getattr(item, 'tooltip_title', ''), - 'type': item.get_icon_class(), - 'id': item.scope_ids.usage_id.to_deprecated_string(), + 'type': item_type, + 'id': usage_id.to_deprecated_string(), 'bookmarked': is_bookmarked, 'path': " > ".join(display_names + [item.display_name_with_default]), } diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py index cb510eee64..bce67c76ff 100644 --- a/lms/djangoapps/courseware/module_render.py +++ b/lms/djangoapps/courseware/module_render.py @@ -14,7 +14,7 @@ from django.core.cache import cache from django.template.context_processors import csrf from django.core.exceptions import PermissionDenied from django.core.urlresolvers import reverse -from django.http import Http404, HttpResponse +from django.http import Http404, HttpResponse, HttpResponseForbidden from django.views.decorators.csrf import csrf_exempt from edx_proctoring.services import ProctoringService from opaque_keys import InvalidKeyError @@ -925,29 +925,32 @@ def handle_xblock_callback(request, course_id, usage_id, handler, suffix=None): Generic view for extensions. This is where AJAX calls go. Arguments: + request (Request): Django request. + course_id (str): Course containing the block + usage_id (str) + handler (str) + suffix (str) - - request -- the django request. - - location -- the module location. Used to look up the XModule instance - - course_id -- defines the course context for this request. - - Return 403 error if the user is not logged in. Raises Http404 if - the location and course_id do not identify a valid module, the module is - not accessible by the user, or the module raises NotFoundError. If the - module raises any other error, it will escape this function. + Raises: + Http404: If the course is not found in the modulestore. """ - if not request.user.is_authenticated(): - return HttpResponse('Unauthenticated', status=403) + # NOTE (CCB): Allow anonymous GET calls (e.g. for transcripts). Modifying this view is simpler than updating + # the XBlocks to use `handle_xblock_callback_noauth`...which is practically identical to this view. + if request.method != 'GET' and not request.user.is_authenticated(): + return HttpResponseForbidden() + + request.user.known = request.user.is_authenticated() try: course_key = CourseKey.from_string(course_id) except InvalidKeyError: - raise Http404("Invalid location") + raise Http404('{} is not a valid course key'.format(course_id)) with modulestore().bulk_operations(course_key): try: course = modulestore().get_course(course_key) except ItemNotFoundError: - raise Http404("invalid location") + raise Http404('{} does not exist in the modulestore'.format(course_id)) return _invoke_xblock_handler(request, course_id, usage_id, handler, suffix, course=course) diff --git a/lms/djangoapps/courseware/tests/test_discussion_xblock.py b/lms/djangoapps/courseware/tests/test_discussion_xblock.py index 39a7a908ac..4bc3b13e81 100644 --- a/lms/djangoapps/courseware/tests/test_discussion_xblock.py +++ b/lms/djangoapps/courseware/tests/test_discussion_xblock.py @@ -57,7 +57,7 @@ class TestDiscussionXBlock(XModuleRenderingTestBase): self.block.xmodule_runtime = mock.Mock() if self.PATCH_DJANGO_USER: - self.django_user_canary = object() + self.django_user_canary = UserFactory() self.django_user_mock = self.add_patcher( mock.patch.object(DiscussionXBlock, "django_user", new_callable=mock.PropertyMock) ) @@ -259,7 +259,7 @@ class TestXBlockInCourse(SharedModuleStoreTestCase): Set up a user, course, and discussion XBlock for use by tests. """ super(TestXBlockInCourse, cls).setUpClass() - cls.user = UserFactory.create() + cls.user = UserFactory() cls.course = ToyCourseFactory.create() cls.course_key = cls.course.id cls.course_usage_key = cls.store.make_course_usage_key(cls.course_key) @@ -380,8 +380,8 @@ class TestXBlockQueryLoad(SharedModuleStoreTestCase): """ Tests that the permissions queries are cached when rendering numerous discussion XBlocks. """ - user = UserFactory.create() - course = ToyCourseFactory.create() + user = UserFactory() + course = ToyCourseFactory() course_key = course.id course_usage_key = self.store.make_course_usage_key(course_key) discussions = [] diff --git a/lms/djangoapps/courseware/tests/test_module_render.py b/lms/djangoapps/courseware/tests/test_module_render.py index bb73e95f0c..f74c4f7003 100644 --- a/lms/djangoapps/courseware/tests/test_module_render.py +++ b/lms/djangoapps/courseware/tests/test_module_render.py @@ -290,7 +290,6 @@ class ModuleRenderTestCase(SharedModuleStoreTestCase, LoginEnrollmentTestCase): ) response = self.client.post(dispatch_url, {'position': 2}) self.assertEquals(403, response.status_code) - self.assertEquals('Unauthenticated', response.content) def test_missing_position_handler(self): """ diff --git a/lms/djangoapps/courseware/tests/test_views.py b/lms/djangoapps/courseware/tests/test_views.py index 341ebc3d23..0056b6cfc5 100644 --- a/lms/djangoapps/courseware/tests/test_views.py +++ b/lms/djangoapps/courseware/tests/test_views.py @@ -11,10 +11,23 @@ from urllib import quote, urlencode from uuid import uuid4 import ddt +from django.conf import settings +from django.contrib.auth.models import AnonymousUser +from django.core.urlresolvers import reverse +from django.http import Http404, HttpResponseBadRequest +from django.test import TestCase +from django.test.client import Client, RequestFactory +from django.test.utils import override_settings from freezegun import freeze_time +from milestones.tests.utils import MilestonesTestCaseMixin from mock import MagicMock, PropertyMock, create_autospec, patch from nose.plugins.attrib import attr +from opaque_keys.edx.keys import CourseKey +from opaque_keys.edx.locations import Location from pytz import UTC +from xblock.core import XBlock +from xblock.fields import Scope, String +from xblock.fragment import Fragment import courseware.views.views as views import shoppingcart @@ -32,19 +45,9 @@ from courseware.tests.factories import GlobalStaffFactory, StudentModuleFactory from courseware.testutils import RenderXBlockTestMixin from courseware.url_helpers import get_redirect_url from courseware.user_state_client import DjangoXBlockUserStateClient -from django.conf import settings -from django.contrib.auth.models import AnonymousUser -from django.core.urlresolvers import reverse -from django.http import Http404, HttpResponseBadRequest -from django.test import TestCase -from django.test.client import Client, RequestFactory -from django.test.utils import override_settings from lms.djangoapps.commerce.utils import EcommerceService # pylint: disable=import-error from lms.djangoapps.grades.config.waffle import waffle as grades_waffle from lms.djangoapps.grades.config.waffle import ASSUME_ZERO_GRADE_IF_ABSENT -from milestones.tests.utils import MilestonesTestCaseMixin -from opaque_keys.edx.keys import CourseKey -from opaque_keys.edx.locations import Location from openedx.core.djangoapps.catalog.tests.factories import CourseFactory as CatalogCourseFactory from openedx.core.djangoapps.catalog.tests.factories import CourseRunFactory, ProgramFactory from openedx.core.djangoapps.content.course_overviews.models import CourseOverview @@ -52,19 +55,17 @@ 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.self_paced.models import SelfPacedConfiguration +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.features.course_experience import COURSE_OUTLINE_PAGE_FLAG from openedx.features.enterprise_support.tests.mixins.enterprise import EnterpriseTestConsentRequired from student.models import CourseEnrollment -from student.tests.factories import AdminFactory, CourseEnrollmentFactory, UserFactory, TEST_PASSWORD +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 xblock.core import XBlock -from xblock.fields import Scope, String -from xblock.fragment import Fragment from xmodule.graders import ShowCorrectness from xmodule.modulestore import ModuleStoreEnum from xmodule.modulestore.django import modulestore @@ -2193,6 +2194,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) @@ -2262,6 +2264,28 @@ class TestIndexView(ModuleStoreTestCase): ) self.assertIn("Activate Block ID: test_block_id", response.content) + 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') + + url = reverse( + 'courseware_section', + kwargs={ + 'course_id': str(course.id), + 'chapter': chapter.url_name, + 'section': section.url_name, + } + ) + response = self.client.get(url, follow=False) + assert response.status_code == 302 + + 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 + @ddt.ddt class TestIndexViewWithVerticalPositions(ModuleStoreTestCase): diff --git a/lms/djangoapps/courseware/views/index.py b/lms/djangoapps/courseware/views/index.py index 9b49ea544a..243db813aa 100644 --- a/lms/djangoapps/courseware/views/index.py +++ b/lms/djangoapps/courseware/views/index.py @@ -8,12 +8,14 @@ import logging import urllib from django.conf import settings -from django.contrib.auth.decorators import login_required from django.contrib.auth.models import User -from django.template.context_processors import csrf +from django.contrib.auth.views import redirect_to_login from django.core.urlresolvers import reverse from django.http import Http404 +from django.template.context_processors import csrf from django.utils.decorators import method_decorator +from django.utils.functional import cached_property +from django.utils.translation import ugettext as _ from django.views.decorators.cache import cache_control from django.views.decorators.csrf import ensure_csrf_cookie from django.views.generic import View @@ -29,19 +31,20 @@ from openedx.core.djangoapps.crawlers.models import CrawlersConfig from openedx.core.djangoapps.lang_pref import LANGUAGE_KEY from openedx.core.djangoapps.monitoring_utils import set_custom_metrics_for_course_key from openedx.core.djangoapps.user_api.preferences.api import get_user_preference -from openedx.core.djangoapps.waffle_utils import WaffleSwitchNamespace +from openedx.core.djangoapps.util.user_messages import PageLevelMessages +from openedx.core.djangoapps.waffle_utils import WaffleSwitchNamespace, WaffleFlagNamespace, CourseWaffleFlag +from openedx.core.djangolib.markup import HTML, Text 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 student.models import CourseEnrollment from util.views import ensure_valid_course_key from xmodule.modulestore.django import modulestore from xmodule.x_module import STUDENT_VIEW - +from .views import CourseTabView from ..access import has_access -from ..access_utils import in_preview_mode, check_course_open_for_learner +from ..access_utils import check_course_open_for_learner from ..courses import get_course_with_access, get_current_child, get_studio_url from ..entrance_exams import ( course_has_entrance_exam, @@ -52,9 +55,6 @@ from ..entrance_exams import ( from ..masquerade import setup_masquerade from ..model_data import FieldDataCache from ..module_render import get_module_for_descriptor, toc_for_course -from .views import ( - CourseTabView, -) log = logging.getLogger("edx.courseware.views.index") @@ -66,7 +66,12 @@ class CoursewareIndex(View): """ View class for the Courseware page. """ - @method_decorator(login_required) + + @cached_property + 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)) @method_decorator(ensure_valid_course_key) @@ -91,7 +96,10 @@ class CoursewareIndex(View): position (unicode): position in module, eg of module """ self.course_key = CourseKey.from_string(course_id) - self.request = request + + 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 self.original_section_url_name = section self.chapter_url_name = chapter @@ -108,11 +116,11 @@ class CoursewareIndex(View): self.course = get_course_with_access( request.user, 'load', self.course_key, depth=CONTENT_DEPTH, - check_if_enrolled=True, + check_if_enrolled=not self.enable_anonymous_courseware_access, ) self.is_staff = has_access(request.user, 'staff', self.course) self._setup_masquerade_for_effective_user() - return self._get(request) + return self.render(request) except Exception as exception: # pylint: disable=broad-except return CourseTabView.handle_exceptions(request, self.course, exception) @@ -131,7 +139,7 @@ class CoursewareIndex(View): # Set the user in the request to the effective user. self.request.user = self.effective_user - def _get(self, request): + def render(self, request): """ Render the index page. """ @@ -148,6 +156,28 @@ class CoursewareIndex(View): self._save_positions() self._prefetch_and_bind_section() + if not request.user.is_authenticated(): + qs = urllib.urlencode({ + 'course_id': self.course_key, + 'enrollment_action': 'enroll', + 'email_opt_in': False, + }) + + PageLevelMessages.register_warning_message( + request, + Text(_("You are not signed in. To see additional course content, {sign_in_link} or " + "{register_link}, and enroll in this course.")).format( + sign_in_link=HTML('{sign_in_label}').format( + sign_in_label=_('sign in'), + url='{}?{}'.format(reverse('signin_user'), qs), + ), + register_link=HTML('{register_label}').format( + register_label=_('register'), + url='{}?{}'.format(reverse('register_user'), qs), + ), + ) + ) + return render_to_response('courseware/courseware.html', self._create_courseware_context(request)) def _redirect_if_not_requested_section(self): @@ -186,15 +216,20 @@ class CoursewareIndex(View): """ Redirect to dashboard if the course is blocked due to non-payment. """ - self.real_user = User.objects.prefetch_related("groups").get(id=self.real_user.id) - redeemed_registration_codes = CourseRegistrationCode.objects.filter( - course_id=self.course_key, - registrationcoderedemption__redeemed_by=self.real_user - ) + redeemed_registration_codes = [] + + if self.request.user.is_authenticated(): + self.real_user = User.objects.prefetch_related("groups").get(id=self.real_user.id) + redeemed_registration_codes = CourseRegistrationCode.objects.filter( + course_id=self.course_key, + registrationcoderedemption__redeemed_by=self.real_user + ) + if is_course_blocked(self.request, redeemed_registration_codes, self.course_key): # registration codes may be generated via Bulk Purchase Scenario # we have to check only for the invoice generated registration codes # that their invoice is valid or not + # TODO Update message to account for the fact that the user is not authenticated. log.warning( u'User %s cannot access the course %s because payment has not yet been received', self.real_user, @@ -218,9 +253,11 @@ class CoursewareIndex(View): """ Returns the preferred language for the actual user making the request. """ - language_preference = get_user_preference(self.real_user, LANGUAGE_KEY) - if not language_preference: - language_preference = settings.LANGUAGE_CODE + language_preference = settings.LANGUAGE_CODE + + if self.request.user.is_authenticated(): + language_preference = get_user_preference(self.real_user, LANGUAGE_KEY) + return language_preference def _is_masquerading_as_student(self): @@ -445,10 +482,15 @@ class CoursewareIndex(View): requested_child=requested_child, ) + # NOTE (CCB): Pull the position from the URL for un-authenticated users. Otherwise, pull the saved + # state from the data store. + position = None if self.request.user.is_authenticated() else self.position section_context = { 'activate_block_id': self.request.GET.get('activate_block_id'), 'requested_child': self.request.GET.get("child"), 'progress_url': reverse('progress', kwargs={'course_id': unicode(self.course_key)}), + 'user_authenticated': self.request.user.is_authenticated(), + 'position': position, } if previous_of_active_section: section_context['prev_url'] = _compute_section_url(previous_of_active_section, 'last') diff --git a/lms/static/js/ajax-error.js b/lms/static/js/ajax-error.js index b25164d2b8..edab7732c4 100644 --- a/lms/static/js/ajax-error.js +++ b/lms/static/js/ajax-error.js @@ -1,5 +1,5 @@ $(document).ajaxError(function(event, jXHR) { - if (jXHR.status === 403 && jXHR.responseText === 'Unauthenticated') { + if (jXHR.status === 403) { var message = gettext( 'You have been logged out of your edX account. ' + 'Click Okay to log in again now. ' + diff --git a/lms/templates/courseware/courseware.html b/lms/templates/courseware/courseware.html index 5c2225ecde..b7ac871917 100644 --- a/lms/templates/courseware/courseware.html +++ b/lms/templates/courseware/courseware.html @@ -94,6 +94,16 @@ from openedx.features.course_experience import course_home_page_title, COURSE_OU var $$course_id = "${course.id | n, js_escaped_string}"; + % if not request.user.is_authenticated(): + + % endif + ${HTML(fragment.foot_html())} diff --git a/lms/templates/discussion/_discussion_inline.html b/lms/templates/discussion/_discussion_inline.html index a1bb344769..27bb8fdd65 100644 --- a/lms/templates/discussion/_discussion_inline.html +++ b/lms/templates/discussion/_discussion_inline.html @@ -13,6 +13,15 @@ from openedx.core.djangolib.js_utils import js_escaped_string data-user-create-comment="${json_dumps(can_create_comment)}" data-user-create-subcomment="${json_dumps(can_create_subcomment)}" data-read-only="${'false' if can_create_thread else 'true'}"> + % if not user.is_authenticated(): +
+ +
+
+ % endif

${_(display_name)}

${_("Topic:")} ${discussion_category} @@ -21,9 +30,12 @@ from openedx.core.djangolib.js_utils import js_escaped_string %endif
- +