From 4f339e2f9ea7a66a5cc4f5693c334ba9cca72ab5 Mon Sep 17 00:00:00 2001 From: Simon Chen Date: Mon, 16 May 2022 10:37:39 -0400 Subject: [PATCH] fix: make the studio user_id the actual anonymous_user_id (#30248) * feat: test out individualized anonymouse_user_id for studio preview on xblocks * test: adds tests for the PreviewModuleSystem anonymous_user_id (#30400) and the INDIVIDUALIZE_ANONYMOUS_USER_ID flag. Co-authored-by: Jillian Vogel Co-authored-by: Simon Chen Co-authored-by: Jillian Vogel --- cms/djangoapps/contentstore/toggles.py | 21 ++++++++++++++ cms/djangoapps/contentstore/views/preview.py | 15 +++++++++- .../contentstore/views/tests/test_preview.py | 28 +++++++++++++++++++ 3 files changed, 63 insertions(+), 1 deletion(-) diff --git a/cms/djangoapps/contentstore/toggles.py b/cms/djangoapps/contentstore/toggles.py index 8e2abc6419..e67e12a6e3 100644 --- a/cms/djangoapps/contentstore/toggles.py +++ b/cms/djangoapps/contentstore/toggles.py @@ -2,6 +2,7 @@ CMS feature toggles. """ from edx_toggles.toggles import SettingDictToggle, WaffleFlag +from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag # .. toggle_name: FEATURES['ENABLE_EXPORT_GIT'] # .. toggle_implementation: SettingDictToggle @@ -138,3 +139,23 @@ def use_new_problem_editor(): Returns a boolean if new problem editor is enabled """ return ENABLE_NEW_PROBLEM_EDITOR_FLAG.is_enabled() + + +# .. toggle_name: contentstore.individualize_anonymous_user_id +# .. toggle_implementation: CourseWaffleFlag +# .. toggle_default: False +# .. toggle_description: This flag enables the use of unique anonymous_user_id during studio preview +# .. toggle_use_cases: temporary +# .. toggle_creation_date: 2022-05-04 +# .. toggle_target_removal_date: 2022-05-30 +# .. toggle_tickets: MST-1455 +INDIVIDUALIZE_ANONYMOUS_USER_ID = CourseWaffleFlag( + f'{CONTENTSTORE_NAMESPACE}.individualize_anonymous_user_id', __name__ +) + + +def individualize_anonymous_user_id(course_id): + """ + Returns a boolean if individualized anonymous_user_id is enabled on the course + """ + return INDIVIDUALIZE_ANONYMOUS_USER_ID.is_enabled(course_id) diff --git a/cms/djangoapps/contentstore/views/preview.py b/cms/djangoapps/contentstore/views/preview.py index c87bf5ac0c..b64a77ddb2 100644 --- a/cms/djangoapps/contentstore/views/preview.py +++ b/cms/djangoapps/contentstore/views/preview.py @@ -27,9 +27,11 @@ from xmodule.util.sandboxing import SandboxService from xmodule.util.xmodule_django import add_webpack_to_fragment from xmodule.x_module import AUTHOR_VIEW, PREVIEW_VIEWS, STUDENT_VIEW, ModuleSystem from cms.djangoapps.xblock_config.models import StudioConfig +from cms.djangoapps.contentstore.toggles import individualize_anonymous_user_id from cms.lib.xblock.field_data import CmsFieldData from common.djangoapps.static_replace.services import ReplaceURLService from common.djangoapps.static_replace.wrapper import replace_urls_wrapper +from common.djangoapps.student.models import anonymous_id_for_user from common.djangoapps.edxmako.shortcuts import render_to_string from common.djangoapps.edxmako.services import MakoService from common.djangoapps.xblock_django.user_service import DjangoXBlockUserService @@ -194,6 +196,17 @@ def _preview_module_system(request, descriptor, field_data): # stick the license wrapper in front wrappers.insert(0, partial(wrap_with_license, mako_service=mako_service)) + preview_anonymous_user_id = 'student' + if individualize_anonymous_user_id(course_id): + # There are blocks (capa, html, and video) where we do not want to scope + # the anonymous_user_id to specific courses. These are captured in the + # block attribute 'requires_per_student_anonymous_id'. Please note, + # the course_id field in AnynomousUserID model is blank if value is None. + if getattr(descriptor, 'requires_per_student_anonymous_id', False): + preview_anonymous_user_id = anonymous_id_for_user(request.user, None) + else: + preview_anonymous_user_id = anonymous_id_for_user(request.user, course_id) + return PreviewModuleSystem( static_url=settings.STATIC_URL, # TODO (cpennington): Do we want to track how instructors are using the preview problems? @@ -216,8 +229,8 @@ def _preview_module_system(request, descriptor, field_data): "settings": SettingsService(), "user": DjangoXBlockUserService( request.user, - anonymous_user_id='student', user_role=get_user_role(request.user, course_id), + anonymous_user_id=preview_anonymous_user_id, ), "partitions": StudioPartitionService(course_id=course_id), "teams_configuration": TeamsConfigurationService(), diff --git a/cms/djangoapps/contentstore/views/tests/test_preview.py b/cms/djangoapps/contentstore/views/tests/test_preview.py index 1f8d9ce51f..66e934911f 100644 --- a/cms/djangoapps/contentstore/views/tests/test_preview.py +++ b/cms/djangoapps/contentstore/views/tests/test_preview.py @@ -9,10 +9,12 @@ from unittest import mock import ddt from django.test.client import Client, RequestFactory from django.test.utils import override_settings +from edx_toggles.toggles.testutils import override_waffle_flag from web_fragments.fragment import Fragment from xblock.core import XBlock, XBlockAside from xmodule.contentstore.django import contentstore +from xmodule.lti_module import LTIBlock from xmodule.modulestore import ModuleStoreEnum from xmodule.modulestore.django import modulestore from xmodule.modulestore.tests.django_utils import ( @@ -21,6 +23,7 @@ from xmodule.modulestore.tests.django_utils import ( from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory from xmodule.modulestore.tests.test_asides import AsideTestType from cms.djangoapps.contentstore.utils import reverse_usage_url +from cms.djangoapps.contentstore.toggles import INDIVIDUALIZE_ANONYMOUS_USER_ID from cms.djangoapps.xblock_config.models import StudioConfig from common.djangoapps import static_replace from common.djangoapps.student.tests.factories import UserFactory @@ -294,3 +297,28 @@ class CmsModuleSystemShimTest(ModuleStoreTestCase): html = '' assert self.runtime.replace_urls(html) == \ static_replace.replace_static_urls(html, course_id=self.runtime.course_id) + + def test_anonymous_user_id_preview(self): + assert self.runtime.anonymous_student_id == 'student' + + @override_waffle_flag(INDIVIDUALIZE_ANONYMOUS_USER_ID, active=True) + def test_anonymous_user_id_individual_per_student(self): + """Test anonymous_user_id on a block which uses per-student anonymous IDs""" + # Create the runtime with the flag turned on. + runtime = _preview_module_system( + self.request, + descriptor=ItemFactory(category="problem", parent=self.course), + field_data=mock.Mock(), + ) + assert runtime.anonymous_student_id == '26262401c528d7c4a6bbeabe0455ec46' + + @override_waffle_flag(INDIVIDUALIZE_ANONYMOUS_USER_ID, active=True) + def test_anonymous_user_id_individual_per_course(self): + """Test anonymous_user_id on a block which uses per-course anonymous IDs""" + # Create the runtime with the flag turned on. + runtime = _preview_module_system( + self.request, + descriptor=ItemFactory(category="lti", parent=self.course, spec=LTIBlock), + field_data=mock.Mock(), + ) + assert runtime.anonymous_student_id == 'cf99fd26f9a41d4d9b4069739cc2be7b'