Exemple #1
0
 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')
Exemple #2
0
 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
Exemple #3
0
 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
Exemple #4
0
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
Exemple #5
0
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),
        },
    )
Exemple #6
0
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(),
        },
    )
Exemple #7
0
    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
Exemple #9
0
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/"
Exemple #11
0
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
Exemple #13
0
    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
Exemple #14
0
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
Exemple #15
0
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
Exemple #16
0
    '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: