def preview_module_system(request, preview_id, descriptor): """ Returns a ModuleSystem for the specified descriptor that is specialized for rendering module previews. request: The active django request preview_id (str): An identifier specifying which preview this module is used for descriptor: An XModuleDescriptor """ def preview_model_data(descriptor): return DbModel( SessionKeyValueStore(request, descriptor._model_data), descriptor.module_class, preview_id, MongoUsage(preview_id, descriptor.location.url()), ) return ModuleSystem( ajax_url=reverse('preview_dispatch', args=[ preview_id, descriptor.location.url( ), '']).rstrip('/'), # TODO (cpennington): Do we want to track how instructors are using the # preview problems? track_function=lambda event_type, event: None, filestore=descriptor.system.resources_fs, get_module=partial(get_preview_module, request, preview_id), render_template=render_from_lms, debug=True, replace_urls=partial(static_replace.replace_static_urls, data_directory=None, course_namespace=descriptor.location), user=request.user, xblock_model_data=preview_model_data, )
def preview_module_system(request, preview_id, descriptor): """ Returns a ModuleSystem for the specified descriptor that is specialized for rendering module previews. request: The active django request preview_id (str): An identifier specifying which preview this module is used for descriptor: An XModuleDescriptor """ def preview_field_data(descriptor): "Helper method to create a DbModel from a descriptor" student_data = DbModel(SessionKeyValueStore(request)) return lms_field_data(descriptor._field_data, student_data) course_id = get_course_for_item(descriptor.location).location.course_id return ModuleSystem( ajax_url=reverse('preview_dispatch', args=[preview_id, descriptor.location.url(), '']).rstrip('/'), # TODO (cpennington): Do we want to track how instructors are using the preview problems? track_function=lambda event_type, event: None, filestore=descriptor.system.resources_fs, get_module=partial(load_preview_module, request, preview_id), render_template=render_from_lms, debug=True, replace_urls=partial(static_replace.replace_static_urls, data_directory=None, course_id=course_id), user=request.user, xblock_field_data=preview_field_data, can_execute_unsafe_code=(lambda: can_execute_unsafe_code(course_id)), mixins=settings.XBLOCK_MIXINS, course_id=course_id, anonymous_student_id='student')
def setUp(self): xmodule.modulestore.django._MODULESTORES = {} self.student = '*****@*****.**' self.instructor = '*****@*****.**' self.password = '******' self.location = 'TestLocation' self.create_account('u1', self.student, self.password) self.create_account('u2', self.instructor, self.password) self.activate_user(self.student) self.activate_user(self.instructor) self.course_id = "edX/toy/2012_Fall" self.toy = modulestore().get_course(self.course_id) location = "i4x://edX/toy/peergrading/init" model_data = {'data': "<peergrading/>", 'location': location} self.mock_service = peer_grading_service.MockPeerGradingService() self.system = ModuleSystem( ajax_url=location, track_function=None, get_module=None, render_template=render_to_string, replace_urls=None, xblock_model_data={}, s3_interface=test_util_open_ended.S3_INTERFACE, open_ended_grading_interface=test_util_open_ended.OPEN_ENDED_GRADING_INTERFACE ) self.descriptor = peer_grading_module.PeerGradingDescriptor(self.system, model_data) model_data = {'location': location} self.peer_module = peer_grading_module.PeerGradingModule(self.system, self.descriptor, model_data) self.peer_module.peer_gs = self.mock_service self.logout()
def test_system(): """ Construct a test ModuleSystem instance. By default, the render_template() method simply returns the repr of the context it is passed. You can override this behavior by monkey patching:: system = test_system() system.render_template = my_render_func where `my_render_func` is a function of the form my_render_func(template, context). """ return ModuleSystem( ajax_url='courses/course_id/modx/a_location', track_function=Mock(), get_module=Mock(), render_template=lambda template, context: repr(context), replace_urls=lambda html: str(html), user=Mock(is_staff=False), filestore=Mock(), debug=True, xqueue={'interface': None, 'callback_url': '/', 'default_queuename': 'testqueue', 'waittime': 10, 'construct_callback' : Mock(side_effect="/")}, node_path=os.environ.get("NODE_PATH", "/usr/local/lib/node_modules"), xblock_model_data=lambda descriptor: descriptor._model_data, anonymous_student_id='student', open_ended_grading_interface= open_ended_grading_interface )
def preview_module_system(request, preview_id, descriptor): """ Returns a ModuleSystem for the specified descriptor that is specialized for rendering module previews. request: The active django request preview_id (str): An identifier specifying which preview this module is used for descriptor: An XModuleDescriptor """ def preview_field_data(descriptor): "Helper method to create a DbModel from a descriptor" student_data = DbModel(SessionKeyValueStore(request)) return lms_field_data(descriptor._field_data, student_data) course_id = get_course_for_item(descriptor.location).location.course_id if descriptor.location.category == 'static_tab': wrapper_template = 'xmodule_tab_display.html' else: wrapper_template = 'xmodule_display.html' return ModuleSystem( ajax_url=reverse('preview_dispatch', args=[preview_id, descriptor.location.url(), '']).rstrip('/'), # TODO (cpennington): Do we want to track how instructors are using the preview problems? track_function=lambda event_type, event: None, filestore=descriptor.runtime.resources_fs, get_module=partial(load_preview_module, request, preview_id), render_template=render_from_lms, debug=True, replace_urls=partial(static_replace.replace_static_urls, data_directory=None, course_id=course_id), user=request.user, xmodule_field_data=preview_field_data, can_execute_unsafe_code=(lambda: can_execute_unsafe_code(course_id)), mixins=settings.XBLOCK_MIXINS, course_id=course_id, anonymous_student_id='student', # Set up functions to modify the fragment produced by student_view wrappers=( # This wrapper wraps the module in the template specified above partial(wrap_xmodule, wrapper_template), # This wrapper replaces urls in the output that start with /static # with the correct course-specific url for the static content partial( replace_static_urls, getattr(descriptor, 'data_dir', descriptor.location.course), course_id=descriptor.location.org + '/' + descriptor.location.course + '/BOGUS_RUN_REPLACE_WHEN_AVAILABLE', ), ))
def leaf_module_runtime(self): runtime = ModuleSystem( render_template=lambda *args, **kwargs: u'{!r}, {!r}'.format(args, kwargs), anonymous_student_id='dummy_anonymous_student_id', open_ended_grading_interface={}, ajax_url='dummy_ajax_url', xmodule_field_data=lambda d: d._field_data, get_module=Mock(), replace_urls=Mock(), track_function=Mock(), ) return runtime
def leaf_module_runtime(self): runtime = ModuleSystem( render_template=mock_render_template, anonymous_student_id='dummy_anonymous_student_id', open_ended_grading_interface={}, static_url='/static', ajax_url='dummy_ajax_url', get_module=Mock(), replace_urls=Mock(), track_function=Mock(), error_descriptor_class=ErrorDescriptor, ) return runtime
def __init__(self, config): config['system'] = ModuleSystem(ajax_url=None, track_function=None, get_module=None, render_template=render_to_string, replace_urls=None, xblock_model_data={}) super(StaffGradingService, self).__init__(config) self.url = config['url'] + config['staff_grading'] self.login_url = self.url + '/login/' self.get_next_url = self.url + '/get_next_submission/' self.save_grade_url = self.url + '/save_grade/' self.get_problem_list_url = self.url + '/get_problem_list/' self.get_notifications_url = self.url + "/get_notifications/"
def get_test_xmodule_for_descriptor(descriptor): """ Attempts to create an xmodule which responds usually correctly from the descriptor. Not guaranteed. :param descriptor: """ module_sys = ModuleSystem( ajax_url='', track_function=None, get_module=None, render_template=render_to_string, replace_urls=None, xblock_model_data=_test_xblock_model_data_accessor(descriptor)) return descriptor.xmodule(module_sys)
def peer_grading_notifications(course, user): system = ModuleSystem( ajax_url=None, track_function=None, get_module=None, render_template=render_to_string, replace_urls=None, xmodule_field_data=DictFieldData({}), ) peer_gs = peer_grading_service.PeerGradingService( settings.OPEN_ENDED_GRADING_INTERFACE, system) pending_grading = False img_path = "" course_id = course.id student_id = unique_id_for_user(user) notification_type = "peer" success, notification_dict = get_value_from_cache(student_id, course_id, notification_type) if success: return notification_dict try: notifications = json.loads( peer_gs.get_notifications(course_id, student_id)) if notifications['success']: if notifications['student_needs_to_peer_grade']: pending_grading = True except: #Non catastrophic error, so no real action notifications = {} #This is a dev_facing_error log.info( "Problem with getting notifications from peer grading service for course {0} user {1}." .format(course_id, student_id)) if pending_grading: img_path = "/static/images/grading_notification.png" notification_dict = { 'pending_grading': pending_grading, 'img_path': img_path, 'response': notifications } set_value_in_cache(student_id, course_id, notification_type, notification_dict) return notification_dict
def setUp(self): self.student = '*****@*****.**' self.instructor = '*****@*****.**' self.password = '******' self.location = 'TestLocation' self.create_account('u1', self.student, self.password) self.create_account('u2', self.instructor, self.password) self.activate_user(self.student) self.activate_user(self.instructor) self.course_id = "edX/toy/2012_Fall" self.toy = modulestore().get_course(self.course_id) location = "i4x://edX/toy/peergrading/init" field_data = DictFieldData({ 'data': "<peergrading/>", 'location': location, 'category': 'peergrading' }) self.mock_service = peer_grading_service.MockPeerGradingService() self.system = ModuleSystem( static_url=settings.STATIC_URL, ajax_url=location, track_function=None, get_module=None, render_template=render_to_string, replace_urls=None, s3_interface=test_util_open_ended.S3_INTERFACE, open_ended_grading_interface=test_util_open_ended. OPEN_ENDED_GRADING_INTERFACE, mixins=settings.XBLOCK_MIXINS, error_descriptor_class=ErrorDescriptor, ) self.descriptor = peer_grading_module.PeerGradingDescriptor( self.system, field_data, ScopeIds(None, None, None, None)) self.descriptor.xmodule_runtime = self.system self.peer_module = self.descriptor self.peer_module.peer_gs = self.mock_service self.logout()
def get_test_system(course_id=''): """ Construct a test ModuleSystem instance. By default, the render_template() method simply returns the repr of the context it is passed. You can override this behavior by monkey patching:: system = get_test_system() system.render_template = my_render_func where `my_render_func` is a function of the form my_render_func(template, context). """ return ModuleSystem( static_url='/static', ajax_url='courses/course_id/modx/a_location', track_function=Mock(), get_module=Mock(), render_template=mock_render_template, replace_urls=str, user=Mock(is_staff=False), filestore=Mock(), debug=True, hostname="edx.org", xqueue={ 'interface': None, 'callback_url': '/', 'default_queuename': 'testqueue', 'waittime': 10, 'construct_callback': Mock(side_effect="/") }, node_path=os.environ.get("NODE_PATH", "/usr/local/lib/node_modules"), anonymous_student_id='student', open_ended_grading_interface=open_ended_grading_interface, course_id=course_id, error_descriptor_class=ErrorDescriptor, )
GRADER_DISPLAY_NAMES = { 'ML': _("AI Assessment"), 'PE': _("Peer Assessment"), 'NA': _("Not yet available"), 'BC': _("Automatic Checker"), 'IN': _("Instructor Assessment"), } STUDENT_ERROR_MESSAGE = _("Error occurred while contacting the grading service. Please notify course staff.") STAFF_ERROR_MESSAGE = _("Error occurred while contacting the grading service. Please notify your edX point of contact.") system = ModuleSystem( static_url='/static', ajax_url=None, track_function=None, get_module=None, render_template=render_to_string, replace_urls=None, ) def generate_problem_url(problem_url_parts, base_course_url): """ From a list of problem url parts generated by search.path_to_location and a base course url, generates a url to a problem @param problem_url_parts: Output of search.path_to_location @param base_course_url: Base url of a given course @return: A path to the problem """ problem_url = base_course_url + "/" for i, part in enumerate(problem_url_parts): if part is not None:
def get_module_for_descriptor_internal(user, descriptor, field_data_cache, course_id, track_function, xqueue_callback_url_prefix, position=None, wrap_xmodule_display=True, grade_bucket_type=None, static_asset_path=''): """ Actually implement get_module, without requiring a request. See get_module() docstring for further details. """ # Short circuit--if the user shouldn't have access, bail without doing any work if not has_access(user, descriptor, 'load', course_id): return None # Setup system context for module instance ajax_url = reverse( 'modx_dispatch', kwargs=dict(course_id=course_id, location=descriptor.location.url(), dispatch=''), ) # Intended use is as {ajax_url}/{dispatch_command}, so get rid of the trailing slash. ajax_url = ajax_url.rstrip('/') def make_xqueue_callback(dispatch='score_update'): # Fully qualified callback URL for external queueing system relative_xqueue_callback_url = reverse( 'xqueue_callback', kwargs=dict(course_id=course_id, userid=str(user.id), mod_id=descriptor.location.url(), 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 } # This is a hacky way to pass settings to the combined open ended xmodule # It needs an S3 interface to upload images to S3 # It needs the open ended grading interface in order to get peer grading to be done # this first checks to see if the descriptor is the correct one, and only sends settings if it is # Get descriptor metadata fields indicating needs for various settings needs_open_ended_interface = getattr(descriptor, "needs_open_ended_interface", False) needs_s3_interface = getattr(descriptor, "needs_s3_interface", False) # Initialize interfaces to None open_ended_grading_interface = None s3_interface = None # Create interfaces if needed if needs_open_ended_interface: open_ended_grading_interface = settings.OPEN_ENDED_GRADING_INTERFACE open_ended_grading_interface[ 'mock_peer_grading'] = settings.MOCK_PEER_GRADING open_ended_grading_interface[ 'mock_staff_grading'] = settings.MOCK_STAFF_GRADING if needs_s3_interface: s3_interface = { 'access_key': getattr(settings, 'AWS_ACCESS_KEY_ID', ''), 'secret_access_key': getattr(settings, 'AWS_SECRET_ACCESS_KEY', ''), 'storage_bucket_name': getattr(settings, 'AWS_STORAGE_BUCKET_NAME', 'openended') } 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, descriptor, field_data_cache, course_id, track_function, make_xqueue_callback, position, wrap_xmodule_display, grade_bucket_type, static_asset_path) def xblock_field_data(descriptor): student_data = DbModel(DjangoKeyValueStore(field_data_cache)) return lms_field_data(descriptor._field_data, student_data) def publish(event): """A function that allows XModules to publish events. This only supports grade changes right now.""" if event.get('event_name') != 'grade': return # Construct the key for the module key = KeyValueStore.Key(scope=Scope.user_state, student_id=user.id, block_scope_id=descriptor.location, field_name='grade') student_module = field_data_cache.find_or_create(key) # Update the grades student_module.grade = event.get('value') student_module.max_grade = event.get('max_value') # Save all changes to the underlying KeyValueStore student_module.save() # Bin score into range and increment stats score_bucket = get_score_bucket(student_module.grade, student_module.max_grade) org, course_num, run = course_id.split("/") tags = [ "org:{0}".format(org), "course:{0}".format(course_num), "run:{0}".format(run), "score_bucket:{0}".format(score_bucket) ] if grade_bucket_type is not None: tags.append('type:%s' % grade_bucket_type) statsd.increment("lms.courseware.question_answered", tags=tags) # 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 system = ModuleSystem( track_function=track_function, render_template=render_to_string, ajax_url=ajax_url, xqueue=xqueue, # TODO (cpennington): Figure out how to share info between systems filestore=descriptor.system.resources_fs, get_module=inner_get_module, user=user, # 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_id=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, 'module_id': '' })), node_path=settings.NODE_PATH, xblock_field_data=xblock_field_data, publish=publish, anonymous_student_id=unique_id_for_user(user), course_id=course_id, open_ended_grading_interface=open_ended_grading_interface, s3_interface=s3_interface, cache=cache, can_execute_unsafe_code=(lambda: can_execute_unsafe_code(course_id)), # TODO: When we merge the descriptor and module systems, we can stop reaching into the mixologist (cpennington) mixins=descriptor.system.mixologist._mixins, ) # pass position specified in URL to module through ModuleSystem system.set('position', position) system.set('DEBUG', settings.DEBUG) if settings.MITX_FEATURES.get('ENABLE_PSYCHOMETRICS'): system.set( 'psychometrics_handler', # set callback for updating PsychometricsData make_psychometrics_data_update_handler(course_id, user, descriptor.location.url())) try: module = descriptor.xmodule(system) except: log.exception( "Error creating module from descriptor {0}".format(descriptor)) # make an ErrorDescriptor -- assuming that the descriptor's system is ok if has_access(user, descriptor.location, 'staff', course_id): err_descriptor_class = ErrorDescriptor else: err_descriptor_class = NonStaffErrorDescriptor err_descriptor = err_descriptor_class.from_descriptor( descriptor, error_msg=exc_info_to_str(sys.exc_info())) # Make an error module return err_descriptor.xmodule(system) system.set('user_is_staff', has_access(user, descriptor.location, 'staff', course_id)) _get_html = module.get_html if wrap_xmodule_display is True: _get_html = wrap_xmodule(module.get_html, module, 'xmodule_display.html') module.get_html = replace_static_urls(_get_html, 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 module.get_html = replace_course_urls(module.get_html, course_id) # this will rewrite intra-courseware links # that use the shorthand /jump_to_id/<id>. This is very helpful # for studio authored courses (compared to the /course/... format) since it is # is durable with respect to moves and the author doesn't need to # know the 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 module.get_html = replace_jump_to_id_urls( module.get_html, course_id, reverse('jump_to_id', kwargs={ 'course_id': course_id, 'module_id': '' })) if settings.MITX_FEATURES.get('DISPLAY_HISTOGRAMS_TO_STAFF'): if has_access(user, module, 'staff', course_id): module.get_html = add_histogram(module.get_html, module, user) # force the module to save after rendering module.get_html = save_module(module.get_html, module) return module
from student.models import unique_id_for_user import open_ended_notifications from xmodule.modulestore.django import modulestore from xmodule.modulestore import search from xmodule.modulestore.exceptions import ItemNotFoundError from django.http import HttpResponse, Http404, HttpResponseRedirect from mitxmako.shortcuts import render_to_string log = logging.getLogger(__name__) system = ModuleSystem(ajax_url=None, track_function=None, get_module=None, render_template=render_to_string, replace_urls=None, xblock_field_data={}) controller_qs = ControllerQueryService(settings.OPEN_ENDED_GRADING_INTERFACE, system) """ Reverses the URL from the name and the course id, and then adds a trailing slash if it does not exist yet """ def _reverse_with_slash(url_name, course_id): ajax_url = _reverse_without_slash(url_name, course_id) if not ajax_url.endswith('/'):
def combined_notifications(course, user): """ Show notifications to a given user for a given course. Get notifications from the cache if possible, or from the grading controller server if not. @param course: The course object for which we are getting notifications @param user: The user object for which we are getting notifications @return: A dictionary with boolean pending_grading (true if there is pending grading), img_path (for notification image), and response (actual response from grading controller server). """ #Set up return values so that we can return them for error cases pending_grading = False img_path = "" notifications = {} notification_dict = { 'pending_grading': pending_grading, 'img_path': img_path, 'response': notifications } #We don't want to show anonymous users anything. if not user.is_authenticated(): return notification_dict #Define a mock modulesystem system = ModuleSystem(ajax_url=None, track_function=None, get_module=None, render_template=render_to_string, replace_urls=None, xblock_model_data={}) #Initialize controller query service using our mock system controller_qs = ControllerQueryService( settings.OPEN_ENDED_GRADING_INTERFACE, system) student_id = unique_id_for_user(user) user_is_staff = has_access(user, course, 'staff') course_id = course.id notification_type = "combined" #See if we have a stored value in the cache success, notification_dict = get_value_from_cache(student_id, course_id, notification_type) if success: return notification_dict #Get the time of the last login of the user last_login = user.last_login last_time_viewed = last_login - datetime.timedelta( seconds=(NOTIFICATION_CACHE_TIME + 60)) try: #Get the notifications from the grading controller controller_response = controller_qs.check_combined_notifications( course.id, student_id, user_is_staff, last_time_viewed) notifications = json.loads(controller_response) if notifications['success']: if notifications['overall_need_to_check']: pending_grading = True except: #Non catastrophic error, so no real action #This is a dev_facing_error log.exception( "Problem with getting notifications from controller query service for course {0} user {1}." .format(course_id, student_id)) if pending_grading: img_path = "/static/images/grading_notification.png" notification_dict = { 'pending_grading': pending_grading, 'img_path': img_path, 'response': notifications } #Store the notifications in the cache set_value_in_cache(student_id, course_id, notification_type, notification_dict) return notification_dict
def get_module_for_descriptor(user, request, descriptor, model_data_cache, course_id, position=None, wrap_xmodule_display=True, grade_bucket_type=None): """ Actually implement get_module. See docstring there for details. """ # allow course staff to masquerade as student if has_access(user, descriptor, 'staff', course_id): setup_masquerade(request, True) # Short circuit--if the user shouldn't have access, bail without doing any work if not has_access(user, descriptor, 'load', course_id): return None # Setup system context for module instance ajax_url = reverse( 'modx_dispatch', kwargs=dict(course_id=course_id, location=descriptor.location.url(), dispatch=''), ) # Intended use is as {ajax_url}/{dispatch_command}, so get rid of the trailing slash. ajax_url = ajax_url.rstrip('/') def make_xqueue_callback(dispatch='score_update'): # Fully qualified callback URL for external queueing system xqueue_callback_url = '{proto}://{host}'.format( host=request.get_host(), proto=request.META.get('HTTP_X_FORWARDED_PROTO', 'https' if request.is_secure() else 'http')) xqueue_callback_url = settings.XQUEUE_INTERFACE.get( 'callback_url', xqueue_callback_url) # allow override xqueue_callback_url += reverse( 'xqueue_callback', kwargs=dict(course_id=course_id, userid=str(user.id), id=descriptor.location.url(), dispatch=dispatch), ) return 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 } #This is a hacky way to pass settings to the combined open ended xmodule #It needs an S3 interface to upload images to S3 #It needs the open ended grading interface in order to get peer grading to be done #this first checks to see if the descriptor is the correct one, and only sends settings if it is #Get descriptor metadata fields indicating needs for various settings needs_open_ended_interface = getattr(descriptor, "needs_open_ended_interface", False) needs_s3_interface = getattr(descriptor, "needs_s3_interface", False) #Initialize interfaces to None open_ended_grading_interface = None s3_interface = None #Create interfaces if needed if needs_open_ended_interface: open_ended_grading_interface = settings.OPEN_ENDED_GRADING_INTERFACE open_ended_grading_interface[ 'mock_peer_grading'] = settings.MOCK_PEER_GRADING open_ended_grading_interface[ 'mock_staff_grading'] = settings.MOCK_STAFF_GRADING if needs_s3_interface: s3_interface = { 'access_key': getattr(settings, 'AWS_ACCESS_KEY_ID', ''), 'secret_access_key': getattr(settings, 'AWS_SECRET_ACCESS_KEY', ''), 'storage_bucket_name': getattr(settings, 'AWS_STORAGE_BUCKET_NAME', 'openended') } def inner_get_module(descriptor): """ Delegate to get_module. It does an access check, so may return None """ return get_module_for_descriptor(user, request, descriptor, model_data_cache, course_id, position) def xblock_model_data(descriptor): return DbModel( LmsKeyValueStore(descriptor._model_data, model_data_cache), descriptor.module_class, user.id, LmsUsage(descriptor.location, descriptor.location)) def publish(event): if event.get('event_name') != 'grade': return student_module, created = StudentModule.objects.get_or_create( course_id=course_id, student=user, module_type=descriptor.location.category, module_state_key=descriptor.location.url(), defaults={'state': '{}'}, ) student_module.grade = event.get('value') student_module.max_grade = event.get('max_value') student_module.save() #Bin score into range and increment stats score_bucket = get_score_bucket(student_module.grade, student_module.max_grade) org, course_num, run = course_id.split("/") tags = [ "org:{0}".format(org), "course:{0}".format(course_num), "run:{0}".format(run), "score_bucket:{0}".format(score_bucket) ] if grade_bucket_type is not None: tags.append('type:%s' % grade_bucket_type) statsd.increment("lms.courseware.question_answered", tags=tags) def can_execute_unsafe_code(): # To decide if we can run unsafe code, we check the course id against # a list of regexes configured on the server. for regex in settings.COURSES_WITH_UNSAFE_CODE: if re.match(regex, course_id): return True return False # 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 system = ModuleSystem( track_function=make_track_function(request), render_template=render_to_string, ajax_url=ajax_url, xqueue=xqueue, # TODO (cpennington): Figure out how to share info between systems filestore=descriptor.system.resources_fs, get_module=inner_get_module, user=user, # 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_namespace=descriptor.location._replace(category=None, name=None), ), node_path=settings.NODE_PATH, xblock_model_data=xblock_model_data, publish=publish, anonymous_student_id=unique_id_for_user(user), course_id=course_id, open_ended_grading_interface=open_ended_grading_interface, s3_interface=s3_interface, cache=cache, can_execute_unsafe_code=can_execute_unsafe_code, ) # pass position specified in URL to module through ModuleSystem system.set('position', position) system.set('DEBUG', settings.DEBUG) if settings.MITX_FEATURES.get('ENABLE_PSYCHOMETRICS'): system.set( 'psychometrics_handler', # set callback for updating PsychometricsData make_psychometrics_data_update_handler(course_id, user, descriptor.location.url())) try: module = descriptor.xmodule(system) except: log.exception( "Error creating module from descriptor {0}".format(descriptor)) # make an ErrorDescriptor -- assuming that the descriptor's system is ok if has_access(user, descriptor.location, 'staff', course_id): err_descriptor_class = ErrorDescriptor else: err_descriptor_class = NonStaffErrorDescriptor err_descriptor = err_descriptor_class.from_xml( str(descriptor), descriptor.system, org=descriptor.location.org, course=descriptor.location.course, error_msg=exc_info_to_str(sys.exc_info())) # Make an error module return err_descriptor.xmodule(system) system.set('user_is_staff', has_access(user, descriptor.location, 'staff', course_id)) _get_html = module.get_html if wrap_xmodule_display == True: _get_html = wrap_xmodule(module.get_html, module, 'xmodule_display.html') module.get_html = replace_static_urls( _get_html, getattr(descriptor, 'data_dir', None), course_namespace=module.location._replace(category=None, name=None)) # Allow URLs of the form '/course/' refer to the root of multicourse directory # hierarchy of this course module.get_html = replace_course_urls(module.get_html, course_id) if settings.MITX_FEATURES.get('DISPLAY_HISTOGRAMS_TO_STAFF'): if has_access(user, module, 'staff', course_id): module.get_html = add_histogram(module.get_html, module, user) return module
def get_module_for_descriptor_internal(user, descriptor, model_data_cache, course_id, track_function, xqueue_callback_url_prefix, position=None, wrap_xmodule_display=True, grade_bucket_type=None): """ Actually implement get_module, without requiring a request. See get_module() docstring for further details. """ # Short circuit--if the user shouldn't have access, bail without doing any work if not has_access(user, descriptor, 'load', course_id): return None # Setup system context for module instance ajax_url = reverse('modx_dispatch', kwargs=dict(course_id=course_id, location=descriptor.location.url(), dispatch=''), ) # Intended use is as {ajax_url}/{dispatch_command}, so get rid of the trailing slash. ajax_url = ajax_url.rstrip('/') def make_xqueue_callback(dispatch='score_update'): # Fully qualified callback URL for external queueing system relative_xqueue_callback_url = reverse('xqueue_callback', kwargs=dict(course_id=course_id, userid=str(user.id), mod_id=descriptor.location.url(), 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 } # This is a hacky way to pass settings to the combined open ended xmodule # It needs an S3 interface to upload images to S3 # It needs the open ended grading interface in order to get peer grading to be done # this first checks to see if the descriptor is the correct one, and only sends settings if it is # Get descriptor metadata fields indicating needs for various settings needs_open_ended_interface = getattr(descriptor, "needs_open_ended_interface", False) needs_s3_interface = getattr(descriptor, "needs_s3_interface", False) # Initialize interfaces to None open_ended_grading_interface = None s3_interface = None # Create interfaces if needed if needs_open_ended_interface: open_ended_grading_interface = settings.OPEN_ENDED_GRADING_INTERFACE open_ended_grading_interface['mock_peer_grading'] = settings.MOCK_PEER_GRADING open_ended_grading_interface['mock_staff_grading'] = settings.MOCK_STAFF_GRADING if needs_s3_interface: s3_interface = { 'access_key': getattr(settings, 'AWS_ACCESS_KEY_ID', ''), 'secret_access_key': getattr(settings, 'AWS_SECRET_ACCESS_KEY', ''), 'storage_bucket_name': getattr(settings, 'AWS_STORAGE_BUCKET_NAME', 'openended') } 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, descriptor, model_data_cache, course_id, track_function, make_xqueue_callback, position, wrap_xmodule_display, grade_bucket_type) def xblock_model_data(descriptor): return DbModel( LmsKeyValueStore(descriptor._model_data, model_data_cache), descriptor.module_class, user.id, LmsUsage(descriptor.location, descriptor.location) ) def publish(event): if event.get('event_name') != 'grade': return student_module, created = StudentModule.objects.get_or_create( course_id=course_id, student=user, module_type=descriptor.location.category, module_state_key=descriptor.location.url(), defaults={'state': '{}'}, ) student_module.grade = event.get('value') student_module.max_grade = event.get('max_value') student_module.save() # Bin score into range and increment stats score_bucket = get_score_bucket(student_module.grade, student_module.max_grade) org, course_num, run = course_id.split("/") tags = ["org:{0}".format(org), "course:{0}".format(course_num), "run:{0}".format(run), "score_bucket:{0}".format(score_bucket)] if grade_bucket_type is not None: tags.append('type:%s' % grade_bucket_type) statsd.increment("lms.courseware.question_answered", tags=tags) # 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 system = ModuleSystem(track_function=track_function, render_template=render_to_string, ajax_url=ajax_url, xqueue=xqueue, # TODO (cpennington): Figure out how to share info between systems filestore=descriptor.system.resources_fs, get_module=inner_get_module, user=user, # 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_namespace=descriptor.location._replace(category=None, name=None), ), node_path=settings.NODE_PATH, xblock_model_data=xblock_model_data, publish=publish, anonymous_student_id=unique_id_for_user(user), course_id=course_id, open_ended_grading_interface=open_ended_grading_interface, s3_interface=s3_interface, cache=cache, can_execute_unsafe_code=(lambda: can_execute_unsafe_code(course_id)), ) # pass position specified in URL to module through ModuleSystem system.set('position', position) system.set('DEBUG', settings.DEBUG) if settings.MITX_FEATURES.get('ENABLE_PSYCHOMETRICS'): system.set('psychometrics_handler', # set callback for updating PsychometricsData make_psychometrics_data_update_handler(course_id, user, descriptor.location.url())) try: module = descriptor.xmodule(system) except: log.exception("Error creating module from descriptor {0}".format(descriptor)) # make an ErrorDescriptor -- assuming that the descriptor's system is ok if has_access(user, descriptor.location, 'staff', course_id): err_descriptor_class = ErrorDescriptor else: err_descriptor_class = NonStaffErrorDescriptor err_descriptor = err_descriptor_class.from_descriptor( descriptor, error_msg=exc_info_to_str(sys.exc_info()) ) # Make an error module return err_descriptor.xmodule(system) system.set('user_is_staff', has_access(user, descriptor.location, 'staff', course_id)) _get_html = module.get_html if wrap_xmodule_display == True: _get_html = wrap_xmodule(module.get_html, module, 'xmodule_display.html') module.get_html = replace_static_urls( _get_html, getattr(descriptor, 'data_dir', None), course_namespace=module.location._replace(category=None, name=None)) # Allow URLs of the form '/course/' refer to the root of multicourse directory # hierarchy of this course module.get_html = replace_course_urls(module.get_html, course_id) if settings.MITX_FEATURES.get('DISPLAY_HISTOGRAMS_TO_STAFF'): if has_access(user, module, 'staff', course_id): module.get_html = add_histogram(module.get_html, module, user) return module
import open_ended_notifications from xmodule.modulestore.django import modulestore from xmodule.modulestore import search from xmodule.modulestore.exceptions import ItemNotFoundError, NoPathToItem from django.http import HttpResponse, Http404, HttpResponseRedirect from mitxmako.shortcuts import render_to_string log = logging.getLogger(__name__) system = ModuleSystem( ajax_url=None, track_function=None, get_module=None, render_template=render_to_string, replace_urls=None, xmodule_field_data=DictFieldData({}), ) controller_qs = ControllerQueryService(settings.OPEN_ENDED_GRADING_INTERFACE, system) """ Reverses the URL from the name and the course id, and then adds a trailing slash if it does not exist yet """ def _reverse_with_slash(url_name, course_id): ajax_url = _reverse_without_slash(url_name, course_id)
def get_module_for_descriptor_internal( user, descriptor, field_data_cache, course_id, track_function, xqueue_callback_url_prefix, position=None, wrap_xmodule_display=True, grade_bucket_type=None, static_asset_path="", ): """ Actually implement get_module, without requiring a request. See get_module() docstring for further details. """ # Short circuit--if the user shouldn't have access, bail without doing any work if not has_access(user, descriptor, "load", course_id): return None # Setup system context for module instance ajax_url = reverse( "modx_dispatch", kwargs=dict(course_id=course_id, location=descriptor.location.url(), dispatch="") ) # Intended use is as {ajax_url}/{dispatch_command}, so get rid of the trailing slash. ajax_url = ajax_url.rstrip("/") def make_xqueue_callback(dispatch="score_update"): # Fully qualified callback URL for external queueing system relative_xqueue_callback_url = reverse( "xqueue_callback", kwargs=dict(course_id=course_id, userid=str(user.id), mod_id=descriptor.location.url(), 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, } # This is a hacky way to pass settings to the combined open ended xmodule # It needs an S3 interface to upload images to S3 # It needs the open ended grading interface in order to get peer grading to be done # this first checks to see if the descriptor is the correct one, and only sends settings if it is # Get descriptor metadata fields indicating needs for various settings needs_open_ended_interface = getattr(descriptor, "needs_open_ended_interface", False) needs_s3_interface = getattr(descriptor, "needs_s3_interface", False) # Initialize interfaces to None open_ended_grading_interface = None s3_interface = None # Create interfaces if needed if needs_open_ended_interface: open_ended_grading_interface = settings.OPEN_ENDED_GRADING_INTERFACE open_ended_grading_interface["mock_peer_grading"] = settings.MOCK_PEER_GRADING open_ended_grading_interface["mock_staff_grading"] = settings.MOCK_STAFF_GRADING if needs_s3_interface: s3_interface = { "access_key": getattr(settings, "AWS_ACCESS_KEY_ID", ""), "secret_access_key": getattr(settings, "AWS_SECRET_ACCESS_KEY", ""), "storage_bucket_name": getattr(settings, "AWS_STORAGE_BUCKET_NAME", ""), } 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, descriptor, field_data_cache, course_id, track_function, make_xqueue_callback, position, wrap_xmodule_display, grade_bucket_type, static_asset_path, ) def xblock_field_data(descriptor): student_data = DbModel(DjangoKeyValueStore(field_data_cache)) return lms_field_data(descriptor._field_data, student_data) def publish(event): """A function that allows XModules to publish events. This only supports grade changes right now.""" if event.get("event_name") != "grade": return # Construct the key for the module key = KeyValueStore.Key( scope=Scope.user_state, student_id=user.id, block_scope_id=descriptor.location, field_name="grade" ) student_module = field_data_cache.find_or_create(key) # Update the grades student_module.grade = event.get("value") student_module.max_grade = event.get("max_value") # Save all changes to the underlying KeyValueStore student_module.save() # Bin score into range and increment stats score_bucket = get_score_bucket(student_module.grade, student_module.max_grade) org, course_num, run = course_id.split("/") tags = [ "org:{0}".format(org), "course:{0}".format(course_num), "run:{0}".format(run), "score_bucket:{0}".format(score_bucket), ] if grade_bucket_type is not None: tags.append("type:%s" % grade_bucket_type) statsd.increment("lms.courseware.question_answered", tags=tags) # 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 system = ModuleSystem( track_function=track_function, render_template=render_to_string, ajax_url=ajax_url, xqueue=xqueue, # TODO (cpennington): Figure out how to share info between systems filestore=descriptor.system.resources_fs, get_module=inner_get_module, user=user, # 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_id=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, "module_id": ""}), ), node_path=settings.NODE_PATH, xblock_field_data=xblock_field_data, publish=publish, anonymous_student_id=unique_id_for_user(user), course_id=course_id, open_ended_grading_interface=open_ended_grading_interface, s3_interface=s3_interface, cache=cache, can_execute_unsafe_code=(lambda: can_execute_unsafe_code(course_id)), # TODO: When we merge the descriptor and module systems, we can stop reaching into the mixologist (cpennington) mixins=descriptor.system.mixologist._mixins, ) # pass position specified in URL to module through ModuleSystem system.set("position", position) system.set("DEBUG", settings.DEBUG) if settings.MITX_FEATURES.get("ENABLE_PSYCHOMETRICS"): system.set( "psychometrics_handler", # set callback for updating PsychometricsData make_psychometrics_data_update_handler(course_id, user, descriptor.location.url()), ) try: module = descriptor.xmodule(system) except: log.exception("Error creating module from descriptor {0}".format(descriptor)) # make an ErrorDescriptor -- assuming that the descriptor's system is ok if has_access(user, descriptor.location, "staff", course_id): err_descriptor_class = ErrorDescriptor else: err_descriptor_class = NonStaffErrorDescriptor err_descriptor = err_descriptor_class.from_descriptor(descriptor, error_msg=exc_info_to_str(sys.exc_info())) # Make an error module return err_descriptor.xmodule(system) system.set("user_is_staff", has_access(user, descriptor.location, "staff", course_id)) _get_html = module.get_html if wrap_xmodule_display is True: _get_html = wrap_xmodule(module.get_html, module, "xmodule_display.html") module.get_html = replace_static_urls( _get_html, 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 module.get_html = replace_course_urls(module.get_html, course_id) # this will rewrite intra-courseware links # that use the shorthand /jump_to_id/<id>. This is very helpful # for studio authored courses (compared to the /course/... format) since it is # is durable with respect to moves and the author doesn't need to # know the 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 module.get_html = replace_jump_to_id_urls( module.get_html, course_id, reverse("jump_to_id", kwargs={"course_id": course_id, "module_id": ""}) ) if settings.MITX_FEATURES.get("DISPLAY_HISTOGRAMS_TO_STAFF"): if has_access(user, module, "staff", course_id): module.get_html = add_histogram(module.get_html, module, user) # force the module to save after rendering module.get_html = save_module(module.get_html, module) return module
def get_module_for_descriptor_internal(user, descriptor, field_data_cache, course_id, track_function, xqueue_callback_url_prefix, position=None, wrap_xmodule_display=True, grade_bucket_type=None, static_asset_path=''): """ Actually implement get_module, without requiring a request. See get_module() docstring for further details. """ # Short circuit--if the user shouldn't have access, bail without doing any work if not has_access(user, descriptor, 'load', course_id): return None student_data = DbModel(DjangoKeyValueStore(field_data_cache)) descriptor._field_data = lms_field_data(descriptor._field_data, student_data) # Setup system context for module instance ajax_url = reverse( 'modx_dispatch', kwargs=dict( course_id=course_id, location=descriptor.location.url(), dispatch='' ), ) # Intended use is as {ajax_url}/{dispatch_command}, so get rid of the trailing slash. ajax_url = ajax_url.rstrip('/') def make_xqueue_callback(dispatch='score_update'): # Fully qualified callback URL for external queueing system relative_xqueue_callback_url = reverse( 'xqueue_callback', kwargs=dict( course_id=course_id, userid=str(user.id), mod_id=descriptor.location.url(), 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 } # This is a hacky way to pass settings to the combined open ended xmodule # It needs an S3 interface to upload images to S3 # It needs the open ended grading interface in order to get peer grading to be done # this first checks to see if the descriptor is the correct one, and only sends settings if it is # Get descriptor metadata fields indicating needs for various settings needs_open_ended_interface = getattr(descriptor, "needs_open_ended_interface", False) needs_s3_interface = getattr(descriptor, "needs_s3_interface", False) # Initialize interfaces to None open_ended_grading_interface = None s3_interface = None # Create interfaces if needed if needs_open_ended_interface: open_ended_grading_interface = settings.OPEN_ENDED_GRADING_INTERFACE open_ended_grading_interface['mock_peer_grading'] = settings.MOCK_PEER_GRADING open_ended_grading_interface['mock_staff_grading'] = settings.MOCK_STAFF_GRADING if needs_s3_interface: s3_interface = { 'access_key': getattr(settings, 'AWS_ACCESS_KEY_ID', ''), 'secret_access_key': getattr(settings, 'AWS_SECRET_ACCESS_KEY', ''), 'storage_bucket_name': getattr(settings, 'AWS_STORAGE_BUCKET_NAME', 'openended') } 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, descriptor, field_data_cache, course_id, track_function, make_xqueue_callback, position, wrap_xmodule_display, grade_bucket_type, static_asset_path) def publish(event): """A function that allows XModules to publish events. This only supports grade changes right now.""" if event.get('event_name') != 'grade': return # Construct the key for the module key = KeyValueStore.Key( scope=Scope.user_state, user_id=user.id, block_scope_id=descriptor.location, field_name='grade' ) student_module = field_data_cache.find_or_create(key) # Update the grades student_module.grade = event.get('value') student_module.max_grade = event.get('max_value') # Save all changes to the underlying KeyValueStore student_module.save() # Bin score into range and increment stats score_bucket = get_score_bucket(student_module.grade, student_module.max_grade) org, course_num, run = course_id.split("/") tags = [ "org:{0}".format(org), "course:{0}".format(course_num), "run:{0}".format(run), "score_bucket:{0}".format(score_bucket) ] if grade_bucket_type is not None: tags.append('type:%s' % grade_bucket_type) dog_stats_api.increment("lms.courseware.question_answered", tags=tags) # 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 = [] # 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(wrap_xblock) # 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, 'module_id': ''}), )) if settings.MITX_FEATURES.get('DISPLAY_HISTOGRAMS_TO_STAFF'): if has_access(user, descriptor, 'staff', course_id): block_wrappers.append(partial(add_histogram, user)) system = ModuleSystem( track_function=track_function, render_template=render_to_string, static_url=settings.STATIC_URL, ajax_url=ajax_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_id=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, 'module_id': ''}) ), node_path=settings.NODE_PATH, publish=publish, anonymous_student_id=unique_id_for_user(user), course_id=course_id, open_ended_grading_interface=open_ended_grading_interface, s3_interface=s3_interface, cache=cache, can_execute_unsafe_code=(lambda: can_execute_unsafe_code(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, ) # pass position specified in URL to module through ModuleSystem system.set('position', position) if settings.MITX_FEATURES.get('ENABLE_PSYCHOMETRICS'): system.set( 'psychometrics_handler', # set callback for updating PsychometricsData make_psychometrics_data_update_handler(course_id, user, descriptor.location.url()) ) system.set('user_is_staff', has_access(user, descriptor.location, 'staff', course_id)) # make an ErrorDescriptor -- assuming that the descriptor's system is ok if has_access(user, descriptor.location, 'staff', course_id): system.error_descriptor_class = ErrorDescriptor else: system.error_descriptor_class = NonStaffErrorDescriptor descriptor.xmodule_runtime = system descriptor.scope_ids = descriptor.scope_ids._replace(user_id=user.id) return descriptor