def test_django_translator_in_use_with_empty_block(self): """ Test: Django default translator should in use if we have an empty block """ i18n_service = ModuleI18nService(None) self.assertEqual(i18n_service.ugettext(self.test_language), 'XYZ-TEST-LANGUAGE')
def service(self, block, service_name): """ Return a service, or None. Services are objects implementing arbitrary other interfaces. """ # TODO: Do these declarations actually help with anything? Maybe this check should # be removed from here and from XBlock.runtime declaration = block.service_declaration(service_name) if declaration is None: raise NoSuchServiceError( f"Service {service_name!r} was not requested.") # Most common service is field-data so check that first: if service_name == "field-data": if block.scope_ids not in self.block_field_datas: try: self.block_field_datas[ block.scope_ids] = self._init_field_data_for_block( block) except: # Don't try again pointlessly every time another field is accessed self.block_field_datas[block.scope_ids] = None raise return self.block_field_datas[block.scope_ids] elif service_name == "completion": context_key = block.scope_ids.usage_id.context_key return CompletionService(user=self.user, context_key=context_key) elif service_name == "i18n": return ModuleI18nService(block=block) # Check if the XBlockRuntimeSystem wants to handle this: service = self.system.get_service(block, service_name) # Otherwise, fall back to the base implementation which loads services # defined in the constructor: if service is None: service = super().service(block, service_name) return service
def __init__(self, system, user): super(XBlockRuntime, self).__init__( id_reader=system.id_reader, mixins=( LmsBlockMixin, # Adds Non-deprecated LMS/Studio functionality XBlockShim, # Adds deprecated LMS/Studio functionality / backwards compatibility ), services={ "i18n": ModuleI18nService(), }, default_class=None, select=None, id_generator=system.id_generator, ) self.system = system self.user = user # self.user_id must be set as a separate attribute since base class sets it: if self.user is None: self.user_id = None elif self.user.is_anonymous: self.user_id = get_xblock_id_for_anonymous_user(user) else: self.user_id = self.user.id self.block_field_datas = {} # dict of FieldData stores for our loaded XBlocks. Key is the block's scope_ids. self.django_field_data_caches = {} # dict of FieldDataCache objects for XBlock with database-based user state
def programs_order_history(user): """ Returns the list of previously purchased orders for a user. Only the orders with PaidCourseRegistration and CourseRegCodeItem are returned. """ order_history_list = [] purchased_order_items = ProgramOrder.objects.filter( user=user, status='purchased').order_by('-purchase_time') for order_item in purchased_order_items: # Avoid repeated entries for the same order id. if order_item.id not in [ item['number'] for item in order_history_list ]: order_history_list.append({ 'number': order_item.id, 'title': order_item.program.name, 'price': float(order_item.program.price), 'receipt_url': reverse( 'openedx.core.djangoapps.micro_masters.views.show_program_receipt', kwargs={'ordernum': order_item.id}), 'order_date': ModuleI18nService().strftime(order_item.purchase_time, 'SHORT_DATE') }) return order_history_list
def _preview_module_system(request, descriptor, field_data): """ Returns a ModuleSystem for the specified descriptor that is specialized for rendering module previews. request: The active django request descriptor: An XModuleDescriptor """ course_id = descriptor.location.course_key display_name_only = (descriptor.category == 'static_tab') wrappers = [ # This wrapper wraps the module in the template specified above partial(wrap_xblock, 'PreviewRuntime', display_name_only=display_name_only, usage_id_serializer=unicode, request_token=request_token(request)), # 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, None, course_id=course_id), _studio_wrap_xblock, ] descriptor.runtime._services[ 'studio_user_permissions'] = StudioPermissionsService(request) # pylint: disable=protected-access return PreviewModuleSystem( static_url=settings.STATIC_URL, # 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), 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, can_execute_unsafe_code=(lambda: can_execute_unsafe_code(course_id)), get_python_lib_zip=( lambda: get_python_lib_zip(contentstore, 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=wrappers, error_descriptor_class=ErrorDescriptor, get_user_role=lambda: get_user_role(request.user, course_id), descriptor_runtime=descriptor.runtime, services={ "i18n": ModuleI18nService(), "field-data": field_data, "library_tools": LibraryToolsService(modulestore()), "user": DjangoXBlockUserService(request.user), }, )
def _preview_module_system(request, descriptor): """ Returns a ModuleSystem for the specified descriptor that is specialized for rendering module previews. request: The active django request descriptor: An XModuleDescriptor """ if isinstance(descriptor.location, Locator): course_location = loc_mapper().translate_locator_to_location( descriptor.location, get_course=True) course_id = course_location.course_id else: course_id = get_course_for_item(descriptor.location).location.course_id display_name_only = (descriptor.category == 'static_tab') wrappers = [ # This wrapper wraps the module in the template specified above partial(wrap_xblock, 'PreviewRuntime', display_name_only=display_name_only), # 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, None, course_id=course_id), _studio_wrap_xblock, ] return PreviewModuleSystem( static_url=settings.STATIC_URL, # 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), 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, 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=wrappers, error_descriptor_class=ErrorDescriptor, # get_user_role accepts a location or a CourseLocator. # If descriptor.location is a CourseLocator, course_id is unused. get_user_role=lambda: get_user_role(request.user, descriptor.location, course_id), descriptor_runtime=descriptor.runtime, services={ "i18n": ModuleI18nService(), }, )
def service(self, block, service_name): """ Return a service, or None. Services are objects implementing arbitrary other interfaces. """ # TODO: Do these declarations actually help with anything? Maybe this check should # be removed from here and from XBlock.runtime declaration = block.service_declaration(service_name) if declaration is None: raise NoSuchServiceError( f"Service {service_name!r} was not requested.") # Most common service is field-data so check that first: if service_name == "field-data": if block.scope_ids not in self.block_field_datas: try: self.block_field_datas[ block.scope_ids] = self._init_field_data_for_block( block) except: # Don't try again pointlessly every time another field is accessed self.block_field_datas[block.scope_ids] = None raise return self.block_field_datas[block.scope_ids] elif service_name == "completion": context_key = block.scope_ids.usage_id.context_key return CompletionService(user=self.user, context_key=context_key) elif service_name == "user": return DjangoXBlockUserService( self.user, # The value should be updated to whether the user is staff in the context when Blockstore runtime adds # support for courses. user_is_staff=self.user.is_staff, anonymous_user_id=self.anonymous_student_id, ) elif service_name == "mako": return MakoService() elif service_name == "i18n": return ModuleI18nService(block=block) elif service_name == 'sandbox': context_key = block.scope_ids.usage_id.context_key return SandboxService(contentstore=contentstore, course_id=context_key) elif service_name == 'cache': return CacheService(cache) # Check if the XBlockRuntimeSystem wants to handle this: service = self.system.get_service(block, service_name) # Otherwise, fall back to the base implementation which loads services # defined in the constructor: if service is None: service = super().service(block, service_name) return service
def peer_grading_notifications(course, user): system = LmsModuleSystem( track_function=None, get_module=None, render_template=render_to_string, replace_urls=None, descriptor_runtime=None, services={ 'i18n': ModuleI18nService(), }, ) 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 order_history(user, **kwargs): """ Returns the list of previously purchased orders for a user. Only the orders with PaidCourseRegistration and CourseRegCodeItem are returned. Params: course_org_filter: Current Microsite's ORG. org_filter_out_set: A list of all other Microsites' ORGs. """ course_org_filter = kwargs[ 'course_org_filter'] if 'course_org_filter' in kwargs else None org_filter_out_set = kwargs[ 'org_filter_out_set'] if 'org_filter_out_set' in kwargs else [] order_history_list = [] purchased_order_items = OrderItem.objects.filter( user=user, status='purchased').select_subclasses().order_by('-fulfilled_time') for order_item in purchased_order_items: try: order_title = order_item.pdf_receipt_display_name except: order_title = None # Avoid repeated entries for the same order id. if order_item.order.id not in [ item['order_id'] for item in order_history_list ]: # If we are in a Microsite, then include the orders having courses attributed (by ORG) to that Microsite. # Conversely, if we are not in a Microsite, then include the orders having courses # not attributed (by ORG) to any Microsite. order_item_course_id = getattr(order_item, 'course_id', None) if order_item_course_id: if (course_org_filter and course_org_filter == order_item_course_id.org) or \ (course_org_filter is None and order_item_course_id.org not in org_filter_out_set): order_history_list.append({ 'order_id': order_item.order.id, 'number': order_item.order.id, 'title': order_title, 'price': float(order_item.order.total_cost), 'receipt_url': reverse('shoppingcart.views.show_receipt', kwargs={'ordernum': order_item.order.id}), 'order_date': ModuleI18nService().strftime( order_item.order.purchase_time, 'SHORT_DATE') }) return order_history_list
def __init__(self, config): config['system'] = LmsModuleSystem( static_url='/static', track_function=None, get_module=None, render_template=render_to_string, replace_urls=None, descriptor_runtime=None, services={ 'i18n': ModuleI18nService(), }, ) 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 order_history(user, **kwargs): """ Returns the list of previously purchased orders for a user. Only the orders with PaidCourseRegistration and CourseRegCodeItem are returned. Params: course_org_filter: A list of the current Site's orgs. org_filter_out_set: A list of all other Sites' orgs. """ course_org_filter = kwargs[ 'course_org_filter'] if 'course_org_filter' in kwargs else None org_filter_out_set = kwargs[ 'org_filter_out_set'] if 'org_filter_out_set' in kwargs else [] order_history_list = [] purchased_order_items = OrderItem.objects.filter( user=user, status='purchased').select_subclasses().order_by('-fulfilled_time') for order_item in purchased_order_items: # Avoid repeated entries for the same order id. if order_item.order.id not in [ item['order_id'] for item in order_history_list ]: order_item_course_id = getattr(order_item, 'course_id', None) if order_item_course_id: if (course_org_filter and order_item_course_id.org in course_org_filter) or \ (course_org_filter is None and order_item_course_id.org not in org_filter_out_set): order_history_list.append({ 'order_id': order_item.order.id, 'receipt_url': reverse('shoppingcart.views.show_receipt', kwargs={'ordernum': order_item.order.id}), 'order_date': ModuleI18nService().strftime( order_item.order.purchase_time, 'SHORT_DATE') }) return order_history_list
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 = LmsModuleSystem( static_url="/static", track_function=None, get_module=None, render_template=render_to_string, replace_urls=None, descriptor_runtime=None, services={ 'i18n': ModuleI18nService(), }, ) #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.get('success'): if (notifications.get('staff_needs_to_grade') or notifications.get('student_needs_to_peer_grade')): pending_grading = True except: #Non catastrophic error, so no real action #This is a dev_facing_error log.exception( u"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 __init__(self, items_data, item_id, date, is_invoice, total_cost, payment_received, balance): """ Accepts the following positional arguments items_data - A list having the following items for each row. item_description - String quantity - Integer list_price - float discount - float item_total - float id - String date - datetime is_invoice - boolean - True (for invoice) or False (for Receipt) total_cost - float payment_received - float balance - float """ # From settings self.currency = settings.PAID_COURSE_REGISTRATION_CURRENCY[0] self.logo_path = configuration_helpers.get_value( "PDF_RECEIPT_LOGO_PATH", settings.PDF_RECEIPT_LOGO_PATH) self.cobrand_logo_path = configuration_helpers.get_value( "PDF_RECEIPT_COBRAND_LOGO_PATH", settings.PDF_RECEIPT_COBRAND_LOGO_PATH) self.tax_label = configuration_helpers.get_value( "PDF_RECEIPT_TAX_ID_LABEL", settings.PDF_RECEIPT_TAX_ID_LABEL) self.tax_id = configuration_helpers.get_value( "PDF_RECEIPT_TAX_ID", settings.PDF_RECEIPT_TAX_ID) self.footer_text = configuration_helpers.get_value( "PDF_RECEIPT_FOOTER_TEXT", settings.PDF_RECEIPT_FOOTER_TEXT) self.disclaimer_text = configuration_helpers.get_value( "PDF_RECEIPT_DISCLAIMER_TEXT", settings.PDF_RECEIPT_DISCLAIMER_TEXT, ) self.billing_address_text = configuration_helpers.get_value( "PDF_RECEIPT_BILLING_ADDRESS", settings.PDF_RECEIPT_BILLING_ADDRESS) self.terms_conditions_text = configuration_helpers.get_value( "PDF_RECEIPT_TERMS_AND_CONDITIONS", settings.PDF_RECEIPT_TERMS_AND_CONDITIONS) self.brand_logo_height = configuration_helpers.get_value( "PDF_RECEIPT_LOGO_HEIGHT_MM", settings.PDF_RECEIPT_LOGO_HEIGHT_MM) * mm self.cobrand_logo_height = configuration_helpers.get_value( "PDF_RECEIPT_COBRAND_LOGO_HEIGHT_MM", settings.PDF_RECEIPT_COBRAND_LOGO_HEIGHT_MM) * mm # From Context self.items_data = items_data self.item_id = item_id self.date = ModuleI18nService().strftime(date, 'SHORT_DATE') self.is_invoice = is_invoice self.total_cost = '{amount:.2f} {currency}'.format( currency=self.currency, amount=total_cost) self.payment_received = '{amount:.2f} {currency}'.format( currency=self.currency, amount=payment_received) self.balance = '{amount:.2f} {currency}'.format(currency=self.currency, amount=balance) # initialize the pdf variables self.margin = 15 * mm self.page_width = letter[0] self.page_height = letter[1] self.min_clearance = 3 * mm self.second_page_available_height = '' self.second_page_start_y_pos = '' self.first_page_available_height = '' self.pdf = None
def get_module_system_for_user(user, field_data_cache, # 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): """ 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 field_data_cache, 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 """ student_data = KvsFieldData(DjangoKeyValueStore(field_data_cache)) 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.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 } # 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=user, descriptor=descriptor, field_data_cache=field_data_cache, 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, ) def _fulfill_content_milestones(user, course_key, content_key): """ Internal helper to handle milestone fulfillments for the specified content module """ # Fulfillment Use Case: Entrance Exam # If this module is part of an entrance exam, we'll need to see if the student # has reached the point at which they can collect the associated milestone if settings.FEATURES.get('ENTRANCE_EXAMS', False): course = modulestore().get_course(course_key) content = modulestore().get_item(content_key) entrance_exam_enabled = getattr(course, 'entrance_exam_enabled', False) in_entrance_exam = getattr(content, 'in_entrance_exam', False) if entrance_exam_enabled and in_entrance_exam: # We don't have access to the true request object in this context, but we can use a mock request = RequestFactory().request() request.user = user exam_pct = get_entrance_exam_score(request, course) if exam_pct >= course.entrance_exam_minimum_score_pct: exam_key = UsageKey.from_string(course.entrance_exam_id) relationship_types = milestones_helpers.get_milestone_relationship_types() content_milestones = milestones_helpers.get_course_content_milestones( course_key, exam_key, relationship=relationship_types['FULFILLS'] ) # Add each milestone to the user's set... user = {'id': request.user.id} for milestone in content_milestones: milestones_helpers.add_user_milestone(user, milestone) def handle_grade_event(block, event_type, event): # pylint: disable=unused-argument """ Manages the workflow for recording and updating of student module grade state """ user_id = event.get('user_id', user.id) # 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) tags = [ u"org:{}".format(course_id.org), u"course:{}".format(course_id), u"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) # Cycle through the milestone fulfillment scenarios to see if any are now applicable # thanks to the updated grading information that was just submitted _fulfill_content_milestones( user, course_id, descriptor.location, ) def publish(block, event_type, event): """A function that allows XModules to publish events.""" if event_type == 'grade': handle_grade_event(block, event_type, event) else: track_function(event_type, event) 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(), ) (inner_system, inner_student_data) = get_module_system_for_user( user=real_user, field_data_cache=field_data_cache_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 ) # rebinds module to a different student. We'll change system, student_data, and scope_ids module.descriptor.bind_for_student( inner_system, LmsFieldData(module.descriptor._field_data, inner_student_data), # pylint: disable=protected-access 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 = [] # 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 has_access(user, 'staff', descriptor, course_id): has_instructor_access = has_access(user, 'instructor', descriptor, course_id) block_wrappers.append(partial(add_staff_markup, user, has_instructor_access)) # 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 = 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, 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)), 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={ 'i18n': ModuleI18nService(), 'fs': xblock.reference.plugins.FSService(), 'field-data': field_data, 'user': DjangoXBlockUserService(user, user_is_staff=user_is_staff), }, 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) if settings.FEATURES.get('ENABLE_PSYCHOMETRICS') and user.is_authenticated(): system.set( 'psychometrics_handler', # set callback for updating PsychometricsData make_psychometrics_data_update_handler(course_id, user, descriptor.location) ) system.set(u'user_is_staff', user_is_staff) system.set(u'user_is_admin', 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', getattr(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 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. """ # Do not check access when it's a noauth request. if getattr(user, 'known', 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 student_data = KvsFieldData(DjangoKeyValueStore(field_data_cache)) 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 handle_grade_event(block, event_type, event): user_id = event.get('user_id', user.id) # 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) course_id_dict = Location.parse_course_id(course_id) tags = [ u"org:{org}".format(**course_id_dict), u"course:{course}".format(**course_id_dict), u"run:{name}".format(**course_id_dict), u"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) def publish(block, event_type, event): """A function that allows XModules to publish events.""" if event_type == 'grade': handle_grade_event(block, event_type, event) else: track_function(event_type, event) # 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(partial(wrap_xblock, 'LmsRuntime', extra_data={'course-id': course_id})) # 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.FEATURES.get('DISPLAY_DEBUG_INFO_TO_STAFF'): if has_access(user, descriptor, 'staff', course_id): block_wrappers.append(partial(add_staff_markup, user)) # 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, '') 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_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=anonymous_student_id, 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, get_real_user=user_by_anonymous_id, services={ 'i18n': ModuleI18nService(), }, get_user_role=lambda: get_user_role(user, course_id), descriptor_runtime=descriptor.runtime, ) # pass position specified in URL to module through ModuleSystem system.set('position', position) if settings.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(u'user_is_staff', has_access(user, descriptor.location, u'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.bind_for_student(system, LmsFieldData(descriptor._field_data, student_data)) # pylint: disable=protected-access descriptor.scope_ids = descriptor.scope_ids._replace(user_id=user.id) # pylint: disable=protected-access return descriptor
'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 = LmsModuleSystem( static_url='/static', track_function=None, get_module=None, render_template=render_to_string, replace_urls=None, descriptor_runtime=None, services={ 'i18n': ModuleI18nService(), }, ) 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: if i == 1: