def wrap_aside(self, block, aside, view, frag, context): """ Creates a div which identifies the aside, points to the original block, and writes out the json_init_args into a script tag. The default implementation creates a frag to wraps frag w/ a div identifying the xblock. If you have javascript, you'll need to override this impl """ if not frag.content: return frag runtime_class = 'LmsRuntime' extra_data = { 'block-id': quote_slashes(unicode(block.scope_ids.usage_id)), 'course-id': quote_slashes(unicode(block.course_id)), 'url-selector': 'asideBaseUrl', 'runtime-class': runtime_class, } if self.request_token: extra_data['request-token'] = self.request_token return wrap_xblock_aside( runtime_class, aside, view, frag, context, usage_id_serializer=unicode, request_token=self.request_token, extra_data=extra_data, )
def expected_handler_url(self, handler): """convenience method to get the reversed handler urls""" return "https://{}{}".format(settings.SITE_NAME, reverse( 'xblock_handler_noauth', args=[ text_type(self.course.id), quote_slashes(text_type(self.lti_published.scope_ids.usage_id).encode('utf-8')), handler ] ))
def expected_handler_url(self, handler): """convenience method to get the reversed handler urls""" return "https://{}{}".format(settings.SITE_NAME, reverse( 'courseware.module_render.handle_xblock_callback_noauth', args=[ self.course.id.to_deprecated_string(), quote_slashes(unicode(self.lti_published.scope_ids.usage_id.to_deprecated_string()).encode('utf-8')), handler ] ))
def handler_url(block, handler_name, suffix='', query='', thirdparty=False): """ This method matches the signature for `xblock.runtime:Runtime.handler_url()` See :method:`xblock.runtime:Runtime.handler_url` """ view_name = 'xblock_handler' if handler_name: # Be sure this is really a handler. # # We're checking the .__class__ instead of the block itself to avoid # auto-proxying from Descriptor -> Module, in case descriptors want # to ask for handler URLs without a student context. func = getattr(block.__class__, handler_name, None) if not func: raise ValueError( u"{!r} is not a function name".format(handler_name)) # Is the following necessary? ProxyAttribute causes an UndefinedContext error # if trying this without the module system. # #if not getattr(func, "_is_xblock_handler", False): # raise ValueError("{!r} is not a handler name".format(handler_name)) if thirdparty: view_name = 'xblock_handler_noauth' url = reverse(view_name, kwargs={ 'course_id': six.text_type(block.location.course_key), 'usage_id': quote_slashes(six.text_type(block.scope_ids.usage_id)), 'handler': handler_name, 'suffix': suffix, }) # If suffix is an empty string, remove the trailing '/' if not suffix: url = url.rstrip('/') # If there is a query string, append it if query: url += '?' + query # If third-party, return fully-qualified url if thirdparty: scheme = "https" if settings.HTTPS == "on" else "http" url = '{scheme}://{host}{path}'.format(scheme=scheme, host=settings.SITE_NAME, path=url) return url
def _section_send_email(course, access): """ Provide data for the corresponding bulk email section """ course_key = course.id # Monkey-patch applicable_aside_types to return no asides for the duration of this render with patch.object(course.runtime, 'applicable_aside_types', null_applicable_aside_types): # This HtmlDescriptor is only being used to generate a nice text editor. html_module = HtmlDescriptor( course.system, DictFieldData({'data': ''}), ScopeIds(None, None, None, course_key.make_usage_key('html', 'fake'))) fragment = course.system.render(html_module, 'studio_view') fragment = wrap_xblock( 'LmsRuntime', html_module, 'studio_view', fragment, None, extra_data={"course-id": unicode(course_key)}, usage_id_serializer=lambda usage_id: quote_slashes(unicode(usage_id)), # Generate a new request_token here at random, because this module isn't connected to any other # xblock rendering. request_token=uuid.uuid1().get_hex()) cohorts = [] if is_course_cohorted(course_key): cohorts = get_course_cohorts(course) email_editor = fragment.content section_data = { 'section_key': 'send_email', 'section_display_name': _('Email'), 'access': access, 'send_email': reverse('send_email', kwargs={'course_id': unicode(course_key)}), 'editor': email_editor, 'cohorts': cohorts, 'default_cohort_name': DEFAULT_COHORT_NAME, 'list_instructor_tasks_url': reverse('list_instructor_tasks', kwargs={'course_id': unicode(course_key)}), 'email_background_tasks_url': reverse('list_background_email_tasks', kwargs={'course_id': unicode(course_key)}), 'email_content_history_url': reverse('list_email_content', kwargs={'course_id': unicode(course_key)}), } return section_data
def get_handler_url(self, handler, xblock_name=None): """ Get url for the specified xblock handler """ if xblock_name is None: xblock_name = TestCrowdsourceHinter.XBLOCK_NAMES[0] return reverse('xblock_handler', kwargs={ 'course_id': str(self.course.id), 'usage_id': quote_slashes(str(self.course.id.make_usage_key('crowdsourcehinter', xblock_name))), 'handler': handler, 'suffix': '' })
def expected_handler_url(self, handler): """convenience method to get the reversed handler urls""" return "https://{}{}".format( settings.SITE_NAME, reverse('courseware.module_render.handle_xblock_callback_noauth', args=[ self.course.id.to_deprecated_string(), quote_slashes( unicode(self.lti_published.scope_ids.usage_id. to_deprecated_string()).encode('utf-8')), handler ]))
def get_handler_url(self, handler, xblock_name=None): """ Get url for the specified xblock handler """ if xblock_name is None: xblock_name = TestRecommender.XBLOCK_NAMES[0] return reverse('xblock_handler', kwargs={ 'course_id': text_type(self.course.id), 'usage_id': quote_slashes(text_type(self.course.id.make_usage_key('recommender', xblock_name))), 'handler': handler, 'suffix': '' })
def _section_send_email(course, access): """ Provide data for the corresponding bulk email section """ course_key = course.id # Monkey-patch applicable_aside_types to return no asides for the duration of this render with patch.object(course.runtime, 'applicable_aside_types', null_applicable_aside_types): # This HtmlBlock is only being used to generate a nice text editor. html_module = HtmlBlock( course.system, DictFieldData({'data': ''}), ScopeIds(None, None, None, course_key.make_usage_key('html', 'fake')) ) fragment = course.system.render(html_module, 'studio_view') fragment = wrap_xblock( 'LmsRuntime', html_module, 'studio_view', fragment, None, extra_data={"course-id": str(course_key)}, usage_id_serializer=lambda usage_id: quote_slashes(str(usage_id)), # Generate a new request_token here at random, because this module isn't connected to any other # xblock rendering. request_token=uuid.uuid1().hex ) cohorts = [] if is_course_cohorted(course_key): cohorts = get_course_cohorts(course) course_modes = [] if not VerifiedTrackCohortedCourse.is_verified_track_cohort_enabled(course_key): course_modes = CourseMode.modes_for_course(course_key, include_expired=True, only_selectable=False) email_editor = fragment.content section_data = { 'section_key': 'send_email', 'section_display_name': _('Email'), 'access': access, 'send_email': reverse('send_email', kwargs={'course_id': str(course_key)}), 'editor': email_editor, 'cohorts': cohorts, 'course_modes': course_modes, 'default_cohort_name': DEFAULT_COHORT_NAME, 'list_instructor_tasks_url': reverse( 'list_instructor_tasks', kwargs={'course_id': str(course_key)} ), 'email_background_tasks_url': reverse( 'list_background_email_tasks', kwargs={'course_id': str(course_key)} ), 'email_content_history_url': reverse( 'list_email_content', kwargs={'course_id': str(course_key)} ), } if settings.FEATURES.get("ENABLE_NEW_BULK_EMAIL_EXPERIENCE", False) is not False: section_data[ "communications_mfe_url" ] = f"{settings.COMMUNICATIONS_MICROFRONTEND_URL}/courses/{str(course_key)}/bulk_email" return section_data
def get_handler_url(self, handler, xblock_name=None): """ Get url for the specified xblock handler """ if xblock_name is None: xblock_name = TestCrowdsourceHinter.XBLOCK_NAMES[0] return reverse('xblock_handler', kwargs={ 'course_id': self.course.id.to_deprecated_string(), 'usage_id': quote_slashes(self.course.id.make_usage_key('crowdsourcehinter', xblock_name). to_deprecated_string()), 'handler': handler, 'suffix': '' })
def handler_url(block, handler_name, suffix='', query='', thirdparty=False): """ This method matches the signature for `xblock.runtime:Runtime.handler_url()` See :method:`xblock.runtime:Runtime.handler_url` """ view_name = 'xblock_handler' if handler_name: # Be sure this is really a handler. # # We're checking the .__class__ instead of the block itself to avoid # auto-proxying from Descriptor -> Module, in case descriptors want # to ask for handler URLs without a student context. func = getattr(block.__class__, handler_name, None) if not func: raise ValueError("{!r} is not a function name".format(handler_name)) # Is the following necessary? ProxyAttribute causes an UndefinedContext error # if trying this without the module system. # #if not getattr(func, "_is_xblock_handler", False): # raise ValueError("{!r} is not a handler name".format(handler_name)) if thirdparty: view_name = 'xblock_handler_noauth' url = reverse(view_name, kwargs={ 'course_id': unicode(block.location.course_key), 'usage_id': quote_slashes(unicode(block.scope_ids.usage_id).encode('utf-8')), 'handler': handler_name, 'suffix': suffix, }) # If suffix is an empty string, remove the trailing '/' if not suffix: url = url.rstrip('/') # If there is a query string, append it if query: url += '?' + query # If third-party, return fully-qualified url if thirdparty: scheme = "https" if settings.HTTPS == "on" else "http" url = '{scheme}://{host}{path}'.format( scheme=scheme, host=settings.SITE_NAME, path=url ) return url
def _section_send_email(course, access): """ Provide data for the corresponding bulk email section """ course_key = course.id # Monkey-patch applicable_aside_types to return no asides for the duration of this render with patch.object(course.runtime, 'applicable_aside_types', null_applicable_aside_types): # This HtmlDescriptor is only being used to generate a nice text editor. html_module = HtmlDescriptor( course.system, DictFieldData({'data': ''}), ScopeIds(None, None, None, course_key.make_usage_key('html', 'fake')) ) fragment = course.system.render(html_module, 'studio_view') fragment = wrap_xblock( 'LmsRuntime', html_module, 'studio_view', fragment, None, extra_data={"course-id": unicode(course_key)}, usage_id_serializer=lambda usage_id: quote_slashes(unicode(usage_id)), # Generate a new request_token here at random, because this module isn't connected to any other # xblock rendering. request_token=uuid.uuid1().get_hex() ) cohorts = [] if is_course_cohorted(course_key): cohorts = get_course_cohorts(course) course_modes = [] if not VerifiedTrackCohortedCourse.is_verified_track_cohort_enabled(course_key): course_modes = CourseMode.modes_for_course(course_key, include_expired=True, only_selectable=False) email_editor = fragment.content section_data = { 'section_key': 'send_email', 'section_display_name': _('Email'), 'access': access, 'send_email': reverse('send_email', kwargs={'course_id': unicode(course_key)}), 'editor': email_editor, 'cohorts': cohorts, 'course_modes': course_modes, 'default_cohort_name': DEFAULT_COHORT_NAME, 'list_instructor_tasks_url': reverse( 'list_instructor_tasks', kwargs={'course_id': unicode(course_key)} ), 'email_background_tasks_url': reverse( 'list_background_email_tasks', kwargs={'course_id': unicode(course_key)} ), 'email_content_history_url': reverse( 'list_email_content', kwargs={'course_id': unicode(course_key)} ), } from openedx.stanford.lms.djangoapps.instructor.views.instructor_dashboard import send_email_section_data section_data.update(send_email_section_data()) return section_data
def handler_url(block, handler_name, suffix='', query='', thirdparty=False): """ This method matches the signature for `xblock.runtime:Runtime.handler_url()` :param block: The block to generate the url for :param handler_name: The handler on that block that the url should resolve to :param suffix: Any path suffix that should be added to the handler url :param query: Any query string that should be added to the handler url (which should not include an initial ? or &) :param thirdparty: If true, return a fully-qualified URL instead of relative URL. This is useful for URLs to be used by third-party services. """ view_name = 'xblock_handler' if handler_name: # Be sure this is really a handler. # # We're checking the .__class__ instead of the block itself to avoid # auto-proxying from Descriptor -> Module, in case descriptors want # to ask for handler URLs without a student context. func = getattr(block.__class__, handler_name, None) if not func: raise ValueError(f"{handler_name!r} is not a function name") if thirdparty: view_name = 'xblock_handler_noauth' url = reverse(view_name, kwargs={ 'course_id': str(block.location.course_key), 'usage_id': quote_slashes(str(block.scope_ids.usage_id)), 'handler': handler_name, 'suffix': suffix, }) # If suffix is an empty string, remove the trailing '/' if not suffix: url = url.rstrip('/') # If there is a query string, append it if query: url += '?' + query # If third-party, return fully-qualified url if thirdparty: scheme = "https" if settings.HTTPS == "on" else "http" url = '{scheme}://{host}{path}'.format(scheme=scheme, host=settings.SITE_NAME, path=url) return url
def render_problem(self, username, problem_url_name): """ Use ajax interface to request html for a problem. """ # make sure that the requested user is logged in, so that the ajax call works # on the right problem: self.login_username(username) # make ajax call: modx_url = reverse('xblock_handler', kwargs={ 'course_id': self.course.id.to_deprecated_string(), 'usage_id': quote_slashes(InstructorTaskModuleTestCase.problem_location(problem_url_name).to_deprecated_string()), 'handler': 'xmodule_handler', 'suffix': 'problem_get', }) resp = self.client.post(modx_url, {}) return resp
def render_problem(self, username, problem_url_name): """ Use ajax interface to request html for a problem. """ # make sure that the requested user is logged in, so that the ajax call works # on the right problem: self.login_username(username) # make ajax call: modx_url = reverse('xblock_handler', kwargs={ 'course_id': text_type(self.course.id), 'usage_id': quote_slashes(text_type(InstructorTaskModuleTestCase.problem_location(problem_url_name))), 'handler': 'xmodule_handler', 'suffix': 'problem_get', }) resp = self.client.post(modx_url, {}) return resp
def modx_url(self, problem_location, dispatch): """ Return the url needed for the desired action. problem_location: location of the problem on which we want some action dispatch: the the action string that gets passed to the view as a kwarg example: 'check_problem' for having responses processed """ return reverse('xblock_handler', kwargs={ 'course_id': text_type(self.course.id), 'usage_id': quote_slashes(text_type(problem_location)), 'handler': 'xmodule_handler', 'suffix': dispatch, })
def modx_url(self, problem_location, dispatch): """ Return the url needed for the desired action. problem_location: location of the problem on which we want some action dispatch: the the action string that gets passed to the view as a kwarg example: 'check_problem' for having responses processed """ return reverse( 'xblock_handler', kwargs={ 'course_id': text_type(self.course.id), 'usage_id': quote_slashes(text_type(problem_location)), 'handler': 'xmodule_handler', 'suffix': dispatch, } )
def submit_student_answer(self, username, problem_url_name, responses): """ Use ajax interface to submit a student answer. Assumes the input list of responses has two values. """ def get_input_id(response_id): """Creates input id using information about the test course and the current problem.""" # Note that this is a capa-specific convention. The form is a version of the problem's # URL, modified so that it can be easily stored in html, prepended with "input-" and # appended with a sequence identifier for the particular response the input goes to. course_key = self.course.id return u'input_i4x-{0}-{1}-problem-{2}_{3}'.format( course_key.org.replace(u'.', u'_'), course_key.course.replace(u'.', u'_'), problem_url_name, response_id) # make sure that the requested user is logged in, so that the ajax call works # on the right problem: self.login_username(username) # make ajax call: modx_url = reverse( 'xblock_handler', kwargs={ 'course_id': text_type(self.course.id), 'usage_id': quote_slashes( text_type( InstructorTaskModuleTestCase.problem_location( problem_url_name, self.course.id))), 'handler': 'xmodule_handler', 'suffix': 'problem_check', }) # assign correct identifier to each response. resp = self.client.post( modx_url, { get_input_id(u'{}_1').format(index): response for index, response in enumerate(responses, 2) }) return resp
def test_xblock_handlers(self, xblock_type, xblock_name, user, status_code): """ Test the ajax calls to the problem xblock to ensure the LMS is sending back the expected response codes on requests when content is gated for audit users (404) and when it is available to audit users (200). Content is always available to verified users. """ problem_location = self.blocks_dict[xblock_name].scope_ids.usage_id url = reverse( 'xblock_handler', kwargs={ 'course_id': six.text_type(self.course.id), 'usage_id': quote_slashes(six.text_type(problem_location)), 'handler': 'xmodule_handler', 'suffix': 'problem_show', } ) self.client.login(username=self.users[user].username, password=TEST_PASSWORD) response = self.client.post(url) self.assertEqual(response.status_code, status_code)
def test_wrap_xblock(self, course_id, data_usage_id): """ Verify that new content is added and the resources are the same. """ fragment = self.create_fragment(u"<h1>Test!</h1>") course = getattr(self, course_id) test_wrap_output = wrap_xblock( runtime_class='TestRuntime', block=course, view='baseview', frag=fragment, context=None, usage_id_serializer=lambda usage_id: quote_slashes(unicode(usage_id)), request_token=uuid.uuid1().get_hex() ) self.assertIsInstance(test_wrap_output, Fragment) self.assertIn('xblock-baseview', test_wrap_output.content) self.assertIn('data-runtime-class="TestRuntime"', test_wrap_output.content) self.assertIn(data_usage_id, test_wrap_output.content) self.assertIn('<h1>Test!</h1>', test_wrap_output.content) self.assertEqual(test_wrap_output.resources[0].data, u'body {background-color:red;}') self.assertEqual(test_wrap_output.resources[1].data, 'alert("Hi!");')
def wrap_aside(self, block, aside, view, frag, context): """ Creates a div which identifies the aside, points to the original block, and writes out the json_init_args into a script tag. The default implementation creates a frag to wraps frag w/ a div identifying the xblock. If you have javascript, you'll need to override this impl """ extra_data = { 'block-id': quote_slashes(unicode(block.scope_ids.usage_id)), 'url-selector': 'asideBaseUrl', 'runtime-class': 'LmsRuntime', } if self.request_token: extra_data['request-token'] = self.request_token return self._wrap_ele( aside, view, frag, extra_data, )
def test_wrap_xblock(self, course_id, data_usage_id): """ Verify that new content is added and the resources are the same. """ fragment = self.create_fragment(u"<h1>Test!</h1>") course = getattr(self, course_id) test_wrap_output = wrap_xblock(runtime_class='TestRuntime', block=course, view='baseview', frag=fragment, context=None, usage_id_serializer=lambda usage_id: quote_slashes(unicode(usage_id)), request_token=uuid.uuid1().get_hex()) self.assertIsInstance(test_wrap_output, Fragment) self.assertIn('xblock-baseview', test_wrap_output.content) self.assertIn('data-runtime-class="TestRuntime"', test_wrap_output.content) self.assertIn(data_usage_id, test_wrap_output.content) self.assertIn('<h1>Test!</h1>', test_wrap_output.content) self.assertEqual(test_wrap_output.resources[0].data, u'body {background-color:red;}') self.assertEqual(test_wrap_output.resources[1].data, 'alert("Hi!");')
def submit_student_answer(self, username, problem_url_name, responses): """ Use ajax interface to submit a student answer. Assumes the input list of responses has two values. """ def get_input_id(response_id): """Creates input id using information about the test course and the current problem.""" # Note that this is a capa-specific convention. The form is a version of the problem's # URL, modified so that it can be easily stored in html, prepended with "input-" and # appended with a sequence identifier for the particular response the input goes to. course_key = self.course.id return u'input_i4x-{0}-{1}-problem-{2}_{3}'.format( course_key.org.replace(u'.', u'_'), course_key.course.replace(u'.', u'_'), problem_url_name, response_id ) # make sure that the requested user is logged in, so that the ajax call works # on the right problem: self.login_username(username) # make ajax call: modx_url = reverse('xblock_handler', kwargs={ 'course_id': text_type(self.course.id), 'usage_id': quote_slashes( text_type(InstructorTaskModuleTestCase.problem_location(problem_url_name, self.course.id)) ), 'handler': 'xmodule_handler', 'suffix': 'problem_check', }) # assign correct identifier to each response. resp = self.client.post(modx_url, { get_input_id(u'{}_1').format(index): response for index, response in enumerate(responses, 2) }) return resp
def test_wrap_xblock(self, course_id, data_usage_id): """ Verify that new content is added and the resources are the same. """ fragment = self.create_fragment("<h1>Test!</h1>") fragment.initialize_js('BlockMain') # wrap_block() sets some attributes only if there is JS. course = getattr(self, course_id) test_wrap_output = wrap_xblock( runtime_class='TestRuntime', block=course, view='baseview', frag=fragment, context={"wrap_xblock_data": {"custom-attribute": "custom-value"}}, usage_id_serializer=lambda usage_id: quote_slashes(str(usage_id)), request_token=uuid.uuid1().hex ) assert isinstance(test_wrap_output, Fragment) assert 'xblock-baseview' in test_wrap_output.content assert 'data-runtime-class="TestRuntime"' in test_wrap_output.content assert data_usage_id in test_wrap_output.content assert '<h1>Test!</h1>' in test_wrap_output.content assert 'data-custom-attribute="custom-value"' in test_wrap_output.content assert test_wrap_output.resources[0].data == 'body {background-color:red;}' assert test_wrap_output.resources[1].data == 'alert("Hi!");'
def test_inverse(self, test_string): self.assertEquals(test_string, unquote_slashes(quote_slashes(test_string)))
def get_module_system_for_user( user, student_data, # TODO # Arguments preceding this comment have user binding, those following don't descriptor, course_id, track_function, xqueue_callback_url_prefix, request_token, position=None, wrap_xmodule_display=True, grade_bucket_type=None, static_asset_path='', user_location=None, disable_staff_debug_info=False, course=None ): """ Helper function that returns a module system and student_data bound to a user and a descriptor. The purpose of this function is to factor out everywhere a user is implicitly bound when creating a module, to allow an existing module to be re-bound to a user. Most of the user bindings happen when creating the closures that feed the instantiation of ModuleSystem. The arguments fall into two categories: those that have explicit or implicit user binding, which are user and student_data, and those don't and are just present so that ModuleSystem can be instantiated, which are all the other arguments. Ultimately, this isn't too different than how get_module_for_descriptor_internal was before refactoring. Arguments: see arguments for get_module() request_token (str): A token unique to the request use by xblock initialization Returns: (LmsModuleSystem, KvsFieldData): (module system, student_data) bound to, primarily, the user and descriptor """ def make_xqueue_callback(dispatch='score_update'): """ Returns fully qualified callback URL for external queueing system """ relative_xqueue_callback_url = reverse( 'xqueue_callback', kwargs=dict( course_id=text_type(course_id), userid=str(user.id), mod_id=text_type(descriptor.location), dispatch=dispatch ), ) return xqueue_callback_url_prefix + relative_xqueue_callback_url # Default queuename is course-specific and is derived from the course that # contains the current module. # TODO: Queuename should be derived from 'course_settings.json' of each course xqueue_default_queuename = descriptor.location.org + '-' + descriptor.location.course xqueue = { 'interface': XQUEUE_INTERFACE, 'construct_callback': make_xqueue_callback, 'default_queuename': xqueue_default_queuename.replace(' ', '_'), 'waittime': settings.XQUEUE_WAITTIME_BETWEEN_REQUESTS } def inner_get_module(descriptor): """ Delegate to get_module_for_descriptor_internal() with all values except `descriptor` set. Because it does an access check, it may return None. """ # TODO: fix this so that make_xqueue_callback uses the descriptor passed into # inner_get_module, not the parent's callback. Add it as an argument.... return get_module_for_descriptor_internal( user=user, descriptor=descriptor, student_data=student_data, course_id=course_id, track_function=track_function, xqueue_callback_url_prefix=xqueue_callback_url_prefix, position=position, wrap_xmodule_display=wrap_xmodule_display, grade_bucket_type=grade_bucket_type, static_asset_path=static_asset_path, user_location=user_location, request_token=request_token, course=course ) def get_event_handler(event_type): """ Return an appropriate function to handle the event. Returns None if no special processing is required. """ handlers = { 'grade': handle_grade_event, } if completion_waffle.waffle().is_enabled(completion_waffle.ENABLE_COMPLETION_TRACKING): handlers.update({ 'completion': handle_completion_event, 'progress': handle_deprecated_progress_event, }) return handlers.get(event_type) def publish(block, event_type, event): """ A function that allows XModules to publish events. """ handle_event = get_event_handler(event_type) if handle_event and not is_masquerading_as_specific_student(user, course_id): handle_event(block, event) else: context = contexts.course_context_from_course_id(course_id) if block.runtime.user_id: context['user_id'] = block.runtime.user_id context['asides'] = {} for aside in block.runtime.get_asides(block): if hasattr(aside, 'get_event_context'): aside_event_info = aside.get_event_context(event_type, event) if aside_event_info is not None: context['asides'][aside.scope_ids.block_type] = aside_event_info with tracker.get_tracker().context(event_type, context): track_function(event_type, event) def handle_completion_event(block, event): """ Submit a completion object for the block. """ if not completion_waffle.waffle().is_enabled(completion_waffle.ENABLE_COMPLETION_TRACKING): raise Http404 else: BlockCompletion.objects.submit_completion( user=user, course_key=course_id, block_key=block.scope_ids.usage_id, completion=event['completion'], ) def handle_grade_event(block, event): """ Submit a grade for the block. """ SCORE_PUBLISHED.send( sender=None, block=block, user=user, raw_earned=event['value'], raw_possible=event['max_value'], only_if_higher=event.get('only_if_higher'), score_deleted=event.get('score_deleted'), grader_response=event.get('grader_response') ) def handle_deprecated_progress_event(block, event): """ DEPRECATED: Submit a completion for the block represented by the progress event. This exists to support the legacy progress extension used by edx-solutions. New XBlocks should not emit these events, but instead emit completion events directly. """ if not completion_waffle.waffle().is_enabled(completion_waffle.ENABLE_COMPLETION_TRACKING): raise Http404 else: requested_user_id = event.get('user_id', user.id) if requested_user_id != user.id: log.warning("{} tried to submit a completion on behalf of {}".format(user, requested_user_id)) return # If blocks explicitly declare support for the new completion API, # we expect them to emit 'completion' events, # and we ignore the deprecated 'progress' events # in order to avoid duplicate work and possibly conflicting semantics. if not getattr(block, 'has_custom_completion', False): BlockCompletion.objects.submit_completion( user=user, course_key=course_id, block_key=block.scope_ids.usage_id, completion=1.0, ) def rebind_noauth_module_to_user(module, real_user): """ A function that allows a module to get re-bound to a real user if it was previously bound to an AnonymousUser. Will only work within a module bound to an AnonymousUser, e.g. one that's instantiated by the noauth_handler. Arguments: module (any xblock type): the module to rebind real_user (django.contrib.auth.models.User): the user to bind to Returns: nothing (but the side effect is that module is re-bound to real_user) """ if user.is_authenticated: err_msg = ("rebind_noauth_module_to_user can only be called from a module bound to " "an anonymous user") log.error(err_msg) raise LmsModuleRenderError(err_msg) field_data_cache_real_user = FieldDataCache.cache_for_descriptor_descendents( course_id, real_user, module.descriptor, asides=XBlockAsidesConfig.possible_asides(), ) student_data_real_user = KvsFieldData(DjangoKeyValueStore(field_data_cache_real_user)) (inner_system, inner_student_data) = get_module_system_for_user( user=real_user, student_data=student_data_real_user, # These have implicit user bindings, rest of args considered not to descriptor=module.descriptor, course_id=course_id, track_function=track_function, xqueue_callback_url_prefix=xqueue_callback_url_prefix, position=position, wrap_xmodule_display=wrap_xmodule_display, grade_bucket_type=grade_bucket_type, static_asset_path=static_asset_path, user_location=user_location, request_token=request_token, course=course ) module.descriptor.bind_for_student( inner_system, real_user.id, [ partial(OverrideFieldData.wrap, real_user, course), partial(LmsFieldData, student_data=inner_student_data), ], ) module.descriptor.scope_ids = ( module.descriptor.scope_ids._replace(user_id=real_user.id) ) module.scope_ids = module.descriptor.scope_ids # this is needed b/c NamedTuples are immutable # now bind the module to the new ModuleSystem instance and vice-versa module.runtime = inner_system inner_system.xmodule_instance = module # Build a list of wrapping functions that will be applied in order # to the Fragment content coming out of the xblocks that are about to be rendered. block_wrappers = [] if is_masquerading_as_specific_student(user, course_id): block_wrappers.append(filter_displayed_blocks) if settings.FEATURES.get("LICENSING", False): block_wrappers.append(wrap_with_license) # Wrap the output display in a single div to allow for the XModule # javascript to be bound correctly if wrap_xmodule_display is True: block_wrappers.append(partial( wrap_xblock, 'LmsRuntime', extra_data={'course-id': text_type(course_id)}, usage_id_serializer=lambda usage_id: quote_slashes(text_type(usage_id)), request_token=request_token, )) # TODO (cpennington): When modules are shared between courses, the static # prefix is going to have to be specific to the module, not the directory # that the xml was loaded from # Rewrite urls beginning in /static to point to course-specific content block_wrappers.append(partial( replace_static_urls, getattr(descriptor, 'data_dir', None), course_id=course_id, static_asset_path=static_asset_path or descriptor.static_asset_path )) # Allow URLs of the form '/course/' refer to the root of multicourse directory # hierarchy of this course block_wrappers.append(partial(replace_course_urls, course_id)) # this will rewrite intra-courseware links (/jump_to_id/<id>). This format # is an improvement over the /course/... format for studio authored courses, # because it is agnostic to course-hierarchy. # NOTE: module_id is empty string here. The 'module_id' will get assigned in the replacement # function, we just need to specify something to get the reverse() to work. block_wrappers.append(partial( replace_jump_to_id_urls, course_id, reverse('jump_to_id', kwargs={'course_id': text_type(course_id), 'module_id': ''}), )) if settings.FEATURES.get('DISPLAY_DEBUG_INFO_TO_STAFF'): if is_masquerading_as_specific_student(user, course_id): # When masquerading as a specific student, we want to show the debug button # unconditionally to enable resetting the state of the student we are masquerading as. # We already know the user has staff access when masquerading is active. staff_access = True # To figure out whether the user has instructor access, we temporarily remove the # masquerade_settings from the real_user. With the masquerading settings in place, # the result would always be "False". masquerade_settings = user.real_user.masquerade_settings del user.real_user.masquerade_settings user.real_user.masquerade_settings = masquerade_settings else: staff_access = has_access(user, 'staff', descriptor, course_id) if staff_access: block_wrappers.append(partial(add_staff_markup, user, disable_staff_debug_info)) # These modules store data using the anonymous_student_id as a key. # To prevent loss of data, we will continue to provide old modules with # the per-student anonymized id (as we have in the past), # while giving selected modules a per-course anonymized id. # As we have the time to manually test more modules, we can add to the list # of modules that get the per-course anonymized id. is_pure_xblock = isinstance(descriptor, XBlock) and not isinstance(descriptor, XModuleDescriptor) module_class = getattr(descriptor, 'module_class', None) is_lti_module = not is_pure_xblock and issubclass(module_class, LTIModule) if is_pure_xblock or is_lti_module: anonymous_student_id = anonymous_id_for_user(user, course_id) else: anonymous_student_id = anonymous_id_for_user(user, None) field_data = LmsFieldData(descriptor._field_data, student_data) # pylint: disable=protected-access user_is_staff = bool(has_access(user, u'staff', descriptor.location, course_id)) system = LmsModuleSystem( track_function=track_function, render_template=render_to_string, static_url=settings.STATIC_URL, xqueue=xqueue, # TODO (cpennington): Figure out how to share info between systems filestore=descriptor.runtime.resources_fs, get_module=inner_get_module, user=user, debug=settings.DEBUG, hostname=settings.SITE_NAME, # TODO (cpennington): This should be removed when all html from # a module is coming through get_html and is therefore covered # by the replace_static_urls code below replace_urls=partial( static_replace.replace_static_urls, data_directory=getattr(descriptor, 'data_dir', None), course_id=course_id, static_asset_path=static_asset_path or descriptor.static_asset_path, ), replace_course_urls=partial( static_replace.replace_course_urls, course_key=course_id ), replace_jump_to_id_urls=partial( static_replace.replace_jump_to_id_urls, course_id=course_id, jump_to_id_base_url=reverse('jump_to_id', kwargs={'course_id': text_type(course_id), 'module_id': ''}) ), node_path=settings.NODE_PATH, publish=publish, anonymous_student_id=anonymous_student_id, course_id=course_id, cache=cache, can_execute_unsafe_code=(lambda: can_execute_unsafe_code(course_id)), get_python_lib_zip=(lambda: get_python_lib_zip(contentstore, course_id)), # TODO: When we merge the descriptor and module systems, we can stop reaching into the mixologist (cpennington) mixins=descriptor.runtime.mixologist._mixins, # pylint: disable=protected-access wrappers=block_wrappers, get_real_user=user_by_anonymous_id, services={ 'fs': FSService(), 'field-data': field_data, 'user': DjangoXBlockUserService(user, user_is_staff=user_is_staff), 'verification': XBlockVerificationService(), 'proctoring': ProctoringService(), 'milestones': milestones_helpers.get_service(), 'credit': CreditService(), 'bookmarks': BookmarksService(user=user), 'gating': GatingService(), }, get_user_role=lambda: get_user_role(user, course_id), descriptor_runtime=descriptor._runtime, # pylint: disable=protected-access rebind_noauth_module_to_user=rebind_noauth_module_to_user, user_location=user_location, request_token=request_token, ) # pass position specified in URL to module through ModuleSystem if position is not None: try: position = int(position) except (ValueError, TypeError): log.exception('Non-integer %r passed as position.', position) position = None system.set('position', position) system.set(u'user_is_staff', user_is_staff) system.set(u'user_is_admin', bool(has_access(user, u'staff', 'global'))) system.set(u'user_is_beta_tester', CourseBetaTesterRole(course_id).has_user(user)) system.set(u'days_early_for_beta', descriptor.days_early_for_beta) # make an ErrorDescriptor -- assuming that the descriptor's system is ok if has_access(user, u'staff', descriptor.location, course_id): system.error_descriptor_class = ErrorDescriptor else: system.error_descriptor_class = NonStaffErrorDescriptor return system, field_data
def test_escaped(self, test_string): self.assertNotIn('/', quote_slashes(test_string))
def test_escaped(self, test_string): assert '/' not in quote_slashes(test_string)
def get_url(self, dispatch): """Return item url with dispatch.""" return reverse( 'xblock_handler', args=(unicode(self.course.id), quote_slashes(self.item_url), 'xmodule_handler', dispatch) )
def get_url(self, dispatch): """Return item url with dispatch.""" return reverse('xblock_handler', args=(unicode(self.course.id), quote_slashes(self.item_url), 'xmodule_handler', dispatch))
def get_module_system_for_user( user, student_data, # TODO # pylint: disable=too-many-statements # Arguments preceding this comment have user binding, those following don't descriptor, course_id, track_function, xqueue_callback_url_prefix, request_token, position=None, wrap_xmodule_display=True, grade_bucket_type=None, static_asset_path='', user_location=None, disable_staff_debug_info=False, course=None ): """ Helper function that returns a module system and student_data bound to a user and a descriptor. The purpose of this function is to factor out everywhere a user is implicitly bound when creating a module, to allow an existing module to be re-bound to a user. Most of the user bindings happen when creating the closures that feed the instantiation of ModuleSystem. The arguments fall into two categories: those that have explicit or implicit user binding, which are user and student_data, and those don't and are just present so that ModuleSystem can be instantiated, which are all the other arguments. Ultimately, this isn't too different than how get_module_for_descriptor_internal was before refactoring. Arguments: see arguments for get_module() request_token (str): A token unique to the request use by xblock initialization Returns: (LmsModuleSystem, KvsFieldData): (module system, student_data) bound to, primarily, the user and descriptor """ def make_xqueue_callback(dispatch='score_update'): """ Returns fully qualified callback URL for external queueing system """ relative_xqueue_callback_url = reverse( 'xqueue_callback', kwargs=dict( course_id=course_id.to_deprecated_string(), userid=str(user.id), mod_id=descriptor.location.to_deprecated_string(), dispatch=dispatch ), ) return xqueue_callback_url_prefix + relative_xqueue_callback_url # Default queuename is course-specific and is derived from the course that # contains the current module. # TODO: Queuename should be derived from 'course_settings.json' of each course xqueue_default_queuename = descriptor.location.org + '-' + descriptor.location.course xqueue = { 'interface': XQUEUE_INTERFACE, 'construct_callback': make_xqueue_callback, 'default_queuename': xqueue_default_queuename.replace(' ', '_'), 'waittime': settings.XQUEUE_WAITTIME_BETWEEN_REQUESTS } def inner_get_module(descriptor): """ Delegate to get_module_for_descriptor_internal() with all values except `descriptor` set. Because it does an access check, it may return None. """ # TODO: fix this so that make_xqueue_callback uses the descriptor passed into # inner_get_module, not the parent's callback. Add it as an argument.... return get_module_for_descriptor_internal( user=user, descriptor=descriptor, student_data=student_data, course_id=course_id, track_function=track_function, xqueue_callback_url_prefix=xqueue_callback_url_prefix, position=position, wrap_xmodule_display=wrap_xmodule_display, grade_bucket_type=grade_bucket_type, static_asset_path=static_asset_path, user_location=user_location, request_token=request_token, course=course ) def get_event_handler(event_type): """ Return an appropriate function to handle the event. Returns None if no special processing is required. """ handlers = { 'completion': handle_completion_event, 'grade': handle_grade_event, 'progress': handle_deprecated_progress_event, } return handlers.get(event_type) def publish(block, event_type, event): """ A function that allows XModules to publish events. """ handle_event = get_event_handler(event_type) if handle_event and not is_masquerading_as_specific_student(user, course_id): handle_event(block, event) else: context = contexts.course_context_from_course_id(course_id) if block.runtime.user_id: context['user_id'] = block.runtime.user_id context['asides'] = {} for aside in block.runtime.get_asides(block): if hasattr(aside, 'get_event_context'): aside_event_info = aside.get_event_context(event_type, event) if aside_event_info is not None: context['asides'][aside.scope_ids.block_type] = aside_event_info with tracker.get_tracker().context(event_type, context): track_function(event_type, event) def handle_completion_event(block, event): """ Submit a completion object for the block. """ if not completion_waffle.waffle().is_enabled(completion_waffle.ENABLE_COMPLETION_TRACKING): raise Http404 else: BlockCompletion.objects.submit_completion( user=user, course_key=course_id, block_key=block.scope_ids.usage_id, completion=event['completion'], ) def handle_grade_event(block, event): """ Submit a grade for the block. """ SCORE_PUBLISHED.send( sender=None, block=block, user=user, raw_earned=event['value'], raw_possible=event['max_value'], only_if_higher=event.get('only_if_higher'), score_deleted=event.get('score_deleted'), ) def handle_deprecated_progress_event(block, event): """ DEPRECATED: Submit a completion for the block represented by the progress event. This exists to support the legacy progress extension used by edx-solutions. New XBlocks should not emit these events, but instead emit completion events directly. """ if not completion_waffle.waffle().is_enabled(completion_waffle.ENABLE_COMPLETION_TRACKING): raise Http404 else: requested_user_id = event.get('user_id', user.id) if requested_user_id != user.id: log.warning("{} tried to submit a completion on behalf of {}".format(user, requested_user_id)) return BlockCompletion.objects.submit_completion( user=user, course_key=course_id, block_key=block.scope_ids.usage_id, completion=1.0, ) def rebind_noauth_module_to_user(module, real_user): """ A function that allows a module to get re-bound to a real user if it was previously bound to an AnonymousUser. Will only work within a module bound to an AnonymousUser, e.g. one that's instantiated by the noauth_handler. Arguments: module (any xblock type): the module to rebind real_user (django.contrib.auth.models.User): the user to bind to Returns: nothing (but the side effect is that module is re-bound to real_user) """ if user.is_authenticated(): err_msg = ("rebind_noauth_module_to_user can only be called from a module bound to " "an anonymous user") log.error(err_msg) raise LmsModuleRenderError(err_msg) field_data_cache_real_user = FieldDataCache.cache_for_descriptor_descendents( course_id, real_user, module.descriptor, asides=XBlockAsidesConfig.possible_asides(), ) student_data_real_user = KvsFieldData(DjangoKeyValueStore(field_data_cache_real_user)) (inner_system, inner_student_data) = get_module_system_for_user( user=real_user, student_data=student_data_real_user, # These have implicit user bindings, rest of args considered not to descriptor=module.descriptor, course_id=course_id, track_function=track_function, xqueue_callback_url_prefix=xqueue_callback_url_prefix, position=position, wrap_xmodule_display=wrap_xmodule_display, grade_bucket_type=grade_bucket_type, static_asset_path=static_asset_path, user_location=user_location, request_token=request_token, course=course ) module.descriptor.bind_for_student( inner_system, real_user.id, [ partial(OverrideFieldData.wrap, real_user, course), partial(LmsFieldData, student_data=inner_student_data), ], ) module.descriptor.scope_ids = ( module.descriptor.scope_ids._replace(user_id=real_user.id) ) module.scope_ids = module.descriptor.scope_ids # this is needed b/c NamedTuples are immutable # now bind the module to the new ModuleSystem instance and vice-versa module.runtime = inner_system inner_system.xmodule_instance = module # Build a list of wrapping functions that will be applied in order # to the Fragment content coming out of the xblocks that are about to be rendered. block_wrappers = [] if is_masquerading_as_specific_student(user, course_id): block_wrappers.append(filter_displayed_blocks) if settings.FEATURES.get("LICENSING", False): block_wrappers.append(wrap_with_license) # Wrap the output display in a single div to allow for the XModule # javascript to be bound correctly if wrap_xmodule_display is True: block_wrappers.append(partial( wrap_xblock, 'LmsRuntime', extra_data={'course-id': course_id.to_deprecated_string()}, usage_id_serializer=lambda usage_id: quote_slashes(usage_id.to_deprecated_string()), request_token=request_token, )) # TODO (cpennington): When modules are shared between courses, the static # prefix is going to have to be specific to the module, not the directory # that the xml was loaded from # Rewrite urls beginning in /static to point to course-specific content block_wrappers.append(partial( replace_static_urls, getattr(descriptor, 'data_dir', None), course_id=course_id, static_asset_path=static_asset_path or descriptor.static_asset_path )) # Allow URLs of the form '/course/' refer to the root of multicourse directory # hierarchy of this course block_wrappers.append(partial(replace_course_urls, course_id)) # this will rewrite intra-courseware links (/jump_to_id/<id>). This format # is an improvement over the /course/... format for studio authored courses, # because it is agnostic to course-hierarchy. # NOTE: module_id is empty string here. The 'module_id' will get assigned in the replacement # function, we just need to specify something to get the reverse() to work. block_wrappers.append(partial( replace_jump_to_id_urls, course_id, reverse('jump_to_id', kwargs={'course_id': course_id.to_deprecated_string(), 'module_id': ''}), )) if settings.FEATURES.get('DISPLAY_DEBUG_INFO_TO_STAFF'): if is_masquerading_as_specific_student(user, course_id): # When masquerading as a specific student, we want to show the debug button # unconditionally to enable resetting the state of the student we are masquerading as. # We already know the user has staff access when masquerading is active. staff_access = True # To figure out whether the user has instructor access, we temporarily remove the # masquerade_settings from the real_user. With the masquerading settings in place, # the result would always be "False". masquerade_settings = user.real_user.masquerade_settings del user.real_user.masquerade_settings instructor_access = bool(has_access(user.real_user, 'instructor', descriptor, course_id)) user.real_user.masquerade_settings = masquerade_settings else: staff_access = has_access(user, 'staff', descriptor, course_id) instructor_access = bool(has_access(user, 'instructor', descriptor, course_id)) if staff_access: block_wrappers.append(partial(add_staff_markup, user, instructor_access, disable_staff_debug_info)) # These modules store data using the anonymous_student_id as a key. # To prevent loss of data, we will continue to provide old modules with # the per-student anonymized id (as we have in the past), # while giving selected modules a per-course anonymized id. # As we have the time to manually test more modules, we can add to the list # of modules that get the per-course anonymized id. is_pure_xblock = isinstance(descriptor, XBlock) and not isinstance(descriptor, XModuleDescriptor) module_class = getattr(descriptor, 'module_class', None) is_lti_module = not is_pure_xblock and issubclass(module_class, LTIModule) if is_pure_xblock or is_lti_module: anonymous_student_id = anonymous_id_for_user(user, course_id) else: anonymous_student_id = anonymous_id_for_user(user, None) field_data = LmsFieldData(descriptor._field_data, student_data) # pylint: disable=protected-access user_is_staff = bool(has_access(user, u'staff', descriptor.location, course_id)) system = LmsModuleSystem( track_function=track_function, render_template=render_to_string, static_url=settings.STATIC_URL, xqueue=xqueue, # TODO (cpennington): Figure out how to share info between systems filestore=descriptor.runtime.resources_fs, get_module=inner_get_module, user=user, debug=settings.DEBUG, hostname=settings.SITE_NAME, # TODO (cpennington): This should be removed when all html from # a module is coming through get_html and is therefore covered # by the replace_static_urls code below replace_urls=partial( static_replace.replace_static_urls, data_directory=getattr(descriptor, 'data_dir', None), course_id=course_id, static_asset_path=static_asset_path or descriptor.static_asset_path, ), replace_course_urls=partial( static_replace.replace_course_urls, course_key=course_id ), replace_jump_to_id_urls=partial( static_replace.replace_jump_to_id_urls, course_id=course_id, jump_to_id_base_url=reverse('jump_to_id', kwargs={'course_id': course_id.to_deprecated_string(), 'module_id': ''}) ), node_path=settings.NODE_PATH, publish=publish, anonymous_student_id=anonymous_student_id, course_id=course_id, cache=cache, can_execute_unsafe_code=(lambda: can_execute_unsafe_code(course_id)), get_python_lib_zip=(lambda: get_python_lib_zip(contentstore, course_id)), # TODO: When we merge the descriptor and module systems, we can stop reaching into the mixologist (cpennington) mixins=descriptor.runtime.mixologist._mixins, # pylint: disable=protected-access wrappers=block_wrappers, get_real_user=user_by_anonymous_id, services={ 'fs': FSService(), 'field-data': field_data, 'user': DjangoXBlockUserService(user, user_is_staff=user_is_staff), 'verification': VerificationService(), 'proctoring': ProctoringService(), 'milestones': milestones_helpers.get_service(), 'credit': CreditService(), 'bookmarks': BookmarksService(user=user), }, get_user_role=lambda: get_user_role(user, course_id), descriptor_runtime=descriptor._runtime, # pylint: disable=protected-access rebind_noauth_module_to_user=rebind_noauth_module_to_user, user_location=user_location, request_token=request_token, ) # pass position specified in URL to module through ModuleSystem if position is not None: try: position = int(position) except (ValueError, TypeError): log.exception('Non-integer %r passed as position.', position) position = None system.set('position', position) system.set(u'user_is_staff', user_is_staff) system.set(u'user_is_admin', bool(has_access(user, u'staff', 'global'))) system.set(u'user_is_beta_tester', CourseBetaTesterRole(course_id).has_user(user)) system.set(u'days_early_for_beta', descriptor.days_early_for_beta) # make an ErrorDescriptor -- assuming that the descriptor's system is ok if has_access(user, u'staff', descriptor.location, course_id): system.error_descriptor_class = ErrorDescriptor else: system.error_descriptor_class = NonStaffErrorDescriptor return system, field_data
def _section_recap(request, course, recap_blocks, access): """Provide data for the Recap dashboard section """ course_key = course.id recap_items = [] recap_fragments = [] # Get list of enrolled students for this course user_list = User.objects.filter( courseenrollment__course_id=course_key, courseenrollment__is_active=1, ).order_by('username').select_related('profile') for block in recap_blocks: recap_items.append({ 'name': block.display_name, 'block_list': block.xblock_list, 'url_base': reverse( 'xblock_view', args=[course.id, block.location, 'recap_blocks_listing_view']), 'make_pdf_json': reverse('xblock_handler', args=[course.id, block.location, 'make_pdf_json']), }) recap_block = recap_blocks[0] block, __ = get_module_by_usage_id(request, unicode(course_key), unicode(recap_block.location), disable_staff_debug_info=True, course=course) # Set up recap instructor dashboard fragment, pass data to the context fragment = block.render('recap_blocks_listing_view', context={ 'recap_items': recap_items, 'users': user_list, }) # Wrap the fragment and get all resources associated with this XBlock view fragment = wrap_xblock( 'LmsRuntime', recap_block, 'recap_blocks_listing_view', fragment, None, extra_data={"course-id": unicode(course_key)}, usage_id_serializer=lambda usage_id: quote_slashes(unicode(usage_id)), # Generate a new request_token here at random, because this module isn't connected to any other # xblock rendering. request_token=uuid.uuid1().get_hex()) section_data = { 'fragment': fragment, 'users': user_list, 'section_key': 'recap', 'section_display_name': _('Recap'), 'access': access, 'course_id': unicode(course_key) } return section_data
def test_inverse(self, test_string): assert test_string == unquote_slashes(quote_slashes(test_string))