Ejemplo n.º 1
0
def generate_course_expired_fragment_from_key(user, course_key):
    """
    Like `generate_course_expired_fragment`, but using a CourseKey instead of
    a CourseOverview and using request-level caching.

    Either returns WebFragment to inject XBlock content into, or None if we
    shouldn't show a course expired message for this user.
    """
    request_cache = RequestCache('generate_course_expired_fragment_from_key')
    cache_key = u'message:{},{}'.format(user.id, course_key)
    cache_response = request_cache.get_cached_response(cache_key)
    if cache_response.is_found:
        cached_message = cache_response.value
        # In this case, there is no message to display.
        if cached_message is None:
            return None
        return generate_fragment_from_message(cached_message)

    course = CourseOverview.get_from_id(course_key)
    message = generate_course_expired_message(user, course)
    request_cache.set(cache_key, message)
    if message is None:
        return None

    return generate_fragment_from_message(message)
Ejemplo n.º 2
0
        def _decorator(*args, **kwargs):
            """
            Arguments:
                args, kwargs: values passed into the wrapped function
            """
            # Check to see if we have a result in cache.  If not, invoke our wrapped
            # function.  Cache and return the result to the caller.
            if request_cache_getter:
                request_cache = request_cache_getter(args, kwargs)
            else:
                request_cache = RequestCache(namespace)

            if request_cache:
                cache_key = _func_call_cache_key(f, arg_map_function, *args,
                                                 **kwargs)
                cached_response = request_cache.get_cached_response(cache_key)
                if cached_response.is_found:
                    return cached_response.value

            result = f(*args, **kwargs)

            if request_cache:
                request_cache.set(cache_key, result)

            return result
Ejemplo n.º 3
0
    def render(self, context=None, request=None):
        """
        This takes a render call with a context (from Django) and translates
        it to a render call on the mako template.

        When rendering a large sequence of XBlocks, we may end up rendering
        hundreds of small templates. Even if context processors aren't very
        expensive individually, they will quickly add up in that situation. To
        help guard against this, we do context processing once for a given
        request and then cache it.
        """
        context_object = self._get_context_object(request)

        request_cache = RequestCache('context_processors')
        cache_response = request_cache.get_cached_response('cp_output')
        if cache_response.is_found:
            context_dictionary = dict(cache_response.value)
        else:
            context_dictionary = self._get_context_processors_output_dict(context_object)
            # The context_dictionary is later updated with template specific
            # variables. There are potentially hundreds of calls to templates
            # rendering and we don't want them to interfere with each other, so
            # we make a copy from the output of the context processors and then
            # recreate a new dict every time we pull from the cache.
            request_cache.set('cp_output', dict(context_dictionary))

        if isinstance(context, Context):
            context_dictionary.update(context.flatten())
        elif context is not None:
            context_dictionary.update(context)

        self._add_core_context(context_dictionary)
        self._evaluate_lazy_csrf_tokens(context_dictionary)

        return self.mako_template.render_unicode(**context_dictionary)
Ejemplo n.º 4
0
        def inner_wrapper(*args, **kwargs):
            """
            Wrapper function to decorate with.
            """
            # Check to see if we have a result in cache.  If not, invoke our wrapped
            # function.  Cache and return the result to the caller.
            request_cache = RequestCache(namespace)
            cache_key = _func_call_cache_key(f, *args, **kwargs)

            cached_response = request_cache.get_cached_response(cache_key)
            if cached_response.is_found:
                return cached_response.value

            result = f(*args, **kwargs)
            request_cache.set(cache_key, result)
            return result
Ejemplo n.º 5
0
def get_first_purchase_offer_banner_fragment_from_key(user, course_key):
    """
    Like `get_first_purchase_offer_banner_fragment`, but using a CourseKey
    instead of a CourseOverview and using request-level caching.

    Either returns WebFragment to inject XBlock content into, or None if we
    shouldn't show a first purchase offer message for this user.
    """
    request_cache = RequestCache('get_first_purchase_offer_banner_fragment_from_key')
    cache_key = u'html:{},{}'.format(user.id, course_key)
    cache_response = request_cache.get_cached_response(cache_key)
    if cache_response.is_found:
        cached_html = cache_response.value
        if cached_html is None:
            return None
        return Fragment(cached_html)

    course = CourseOverview.get_from_id(course_key)
    offer_html = generate_offer_html(user, course)
    request_cache.set(cache_key, offer_html)

    return Fragment(offer_html)
Ejemplo n.º 6
0
        def _decorator(*args, **kwargs):
            """
            Arguments:
                args, kwargs: values passed into the wrapped function
            """
            # Check to see if we have a result in cache.  If not, invoke our wrapped
            # function.  Cache and return the result to the caller.
            if request_cache_getter:
                request_cache = request_cache_getter(args, kwargs)
            else:
                request_cache = RequestCache(namespace)

            if request_cache:
                cache_key = _func_call_cache_key(f, arg_map_function, *args, **kwargs)
                cached_response = request_cache.get_cached_response(cache_key)
                if cached_response.is_found:
                    return cached_response.value

            result = f(*args, **kwargs)

            if request_cache:
                request_cache.set(cache_key, result)

            return result
Ejemplo n.º 7
0
    def get_bucket(self, course_key=None, track=True):
        """
        Return which bucket number the specified user is in.

        Bucket 0 is assumed to be the control bucket and will be returned if the experiment is not enabled for
        this user and course.
        """
        # Keep some imports in here, because this class is commonly used at a module level, and we want to avoid
        # circular imports for any models.
        from experiments.models import ExperimentKeyValue
        from student.models import CourseEnrollment

        request = get_current_request()
        if not request:
            return 0

        if not request.user.id:
            # We need username for stable bucketing and id for tracking, so just skip anonymous (not-logged-in) users
            return 0

        # Use course key in experiment name to separate caches and segment calls per-course-run
        experiment_name = self.namespaced_flag_name + ('.{}'.format(course_key)
                                                       if course_key else '')

        # Check if we have a cache for this request already
        request_cache = RequestCache('experiments')
        cache_response = request_cache.get_cached_response(experiment_name)
        if cache_response.is_found:
            return cache_response.value

        # Check if the main flag is even enabled for this user and course.
        if not self._is_enabled(
                course_key):  # grabs user from the current request, if any
            return self._cache_bucket(experiment_name, 0)

        # Check if the enrollment should even be considered (if it started before the experiment wants, we ignore)
        if course_key and self.experiment_id is not None:
            start_val = ExperimentKeyValue.objects.filter(
                experiment_id=self.experiment_id, key='enrollment_start')
            if start_val:
                try:
                    start_date = dateutil.parser.parse(
                        start_val.first().value).replace(tzinfo=pytz.UTC)
                except ValueError:
                    log.exception(
                        'Could not parse enrollment start date for experiment %d',
                        self.experiment_id)
                    return self._cache_bucket(experiment_name, 0)
                enrollment = CourseEnrollment.get_enrollment(
                    request.user, course_key)
                # Only bail if they have an enrollment and it's old -- if they don't have an enrollment, we want to do
                # normal bucketing -- consider the case where the experiment has bits that show before you enroll. We
                # want to keep your bucketing stable before and after you do enroll.
                if enrollment and enrollment.created < start_date:
                    return self._cache_bucket(experiment_name, 0)

        bucket = stable_bucketing_hash_group(experiment_name, self.num_buckets,
                                             request.user.username)

        # Now check if the user is forced into a particular bucket, using our subordinate bucket flags
        for i, bucket_flag in enumerate(self.bucket_flags):
            if bucket_flag.is_enabled(course_key):
                bucket = i
                break

        session_key = 'tracked.{}'.format(experiment_name)
        if track and hasattr(request,
                             'session') and session_key not in request.session:
            segment.track(user_id=request.user.id,
                          event_name='edx.bi.experiment.user.bucketed',
                          properties={
                              'site': request.site.domain,
                              'app_label': self.waffle_namespace.name,
                              'experiment': self.flag_name,
                              'course_id':
                              str(course_key) if course_key else None,
                              'bucket': bucket,
                              'is_staff': request.user.is_staff,
                              'nonInteraction': 1,
                          })

            # Mark that we've recorded this bucketing, so that we don't do it again this session
            request.session[session_key] = True

        return self._cache_bucket(experiment_name, bucket)
Ejemplo n.º 8
0
def _log_and_monitor_expected_errors(request, exception, caller):
    """
    Adds logging and monitoring for expected errors as needed.

    Arguments:
        request: The request
        exception: The exception
        caller: Either 'middleware' or 'drf`
    """
    expected_error_settings_dict = _get_expected_error_settings_dict()
    if not expected_error_settings_dict:
        return

    # 'module.Class', for example, 'django.core.exceptions.PermissionDenied'
    # Note: `Exception` itself doesn't have a module.
    exception_module = getattr(exception, '__module__', '')
    separator = '.' if exception_module else ''
    module_and_class = f'{exception_module}{separator}{exception.__class__.__name__}'

    # Set checked_error_expected_from custom attribute to potentially help find issues where errors are never processed.
    set_custom_attribute('checked_error_expected_from', caller)

    # check if we already added logging/monitoring from a different caller
    request_cache = RequestCache('openedx.core.lib.request_utils')
    cached_handled_exception = request_cache.get_cached_response(
        'handled_exception')
    if cached_handled_exception.is_found:
        cached_module_and_class = cached_handled_exception.value
        # exception was already processed by a different caller
        if cached_handled_exception.value == module_and_class:
            set_custom_attribute('checked_error_expected_from', 'multiple')
            return

        # We have confirmed using monitoring that it is very rare that middleware and drf handle different uncaught exceptions.
        # We will leave this attribute in place, but it is not worth investing in a workaround, especially given that
        # New Relic now offers its own expected error functionality, and this functionality may be simplified or removed.
        set_custom_attribute('unexpected_multiple_exceptions',
                             cached_module_and_class)
        log.warning(
            "Unexpected scenario where different exceptions are handled by _log_and_monitor_expected_errors. "
            "See 'unexpected_multiple_exceptions' custom attribute. Skipping exception for %s.",
            module_and_class,
        )
        return
    request_cache.set('handled_exception', module_and_class)

    if module_and_class not in expected_error_settings_dict:
        return

    exception_message = str(exception)
    set_custom_attribute('error_expected', True)

    expected_error_settings = expected_error_settings_dict[module_and_class]
    if expected_error_settings['is_ignored']:
        # Additional error details are needed for ignored errors, because they are otherwise
        # not available by our monitoring system, because they have been ignored.
        set_custom_attribute('error_ignored_class', module_and_class)
        set_custom_attribute('error_ignored_message', exception_message)

    if expected_error_settings['log_error']:
        exc_info = exception if expected_error_settings[
            'log_stack_trace'] else None
        request_path = getattr(request, 'path', 'request-path-unknown')
        log.info(
            'Expected error %s: %s: seen for path %s',
            module_and_class,
            exception_message,
            request_path,
            exc_info=exc_info,
        )
Ejemplo n.º 9
0
    def get_bucket(self, course_key=None, track=True):
        """
        Return which bucket number the specified user is in.

        The user may be force-bucketed if matching subordinate flags of the form
        "main_flag.BUCKET_NUM" exist. Otherwise, they will be hashed into a default
        bucket based on their username, the experiment name, and the course-run key.

        If `self.use_course_aware_bucketing` is False, the course-run key will
        be omitted from the hashing formula, thus making it so a given user
        has the same default bucket across all course runs; however, subordinate
        flags that match the course-run key will still apply.

        If `course_key` argument is omitted altogether, then subordinate flags
        will be evaluated outside of the course-run context, and the default bucket
        will be calculated as if `self.use_course_aware_bucketing` is False.

        Finally, Bucket 0 is assumed to be the control bucket and will be returned if the
        experiment is not enabled for this user and course.

        Arguments:
            course_key (Optional[CourseKey])
            track (bool):
                Whether an analytics event should be generated if the user is
                bucketed for the first time.

        Returns: int
        """
        # Keep some imports in here, because this class is commonly used at a module level, and we want to avoid
        # circular imports for any models.
        from lms.djangoapps.experiments.models import ExperimentKeyValue
        from lms.djangoapps.courseware.masquerade import get_specific_masquerading_user

        request = get_current_request()
        if not request:
            return 0

        if not hasattr(request, 'user') or not request.user.id:
            # We need username for stable bucketing and id for tracking, so just skip anonymous (not-logged-in) users
            return 0

        user = get_specific_masquerading_user(request.user, course_key)
        if user is None:
            user = request.user
            masquerading_as_specific_student = False
        else:
            masquerading_as_specific_student = True

        # If a course key is passed in, include it in the experiment name
        # in order to separate caches and analytics calls per course-run.
        # If we are using course-aware bucketing, then also append that course key
        # to `bucketing_group_name`, such that users can be hashed into different
        # buckets for different course-runs.
        experiment_name = bucketing_group_name = self.namespaced_flag_name
        if course_key:
            experiment_name += ".{}".format(course_key)
        if course_key and self.use_course_aware_bucketing:
            bucketing_group_name += ".{}".format(course_key)

        # Check if we have a cache for this request already
        request_cache = RequestCache('experiments')
        cache_response = request_cache.get_cached_response(experiment_name)
        if cache_response.is_found:
            return cache_response.value

        # Check if the main flag is even enabled for this user and course.
        if not self.is_experiment_on(
                course_key):  # grabs user from the current request, if any
            return self._cache_bucket(experiment_name, 0)

        # Check if the enrollment should even be considered (if it started before the experiment wants, we ignore)
        if course_key and self.experiment_id is not None:
            values = ExperimentKeyValue.objects.filter(
                experiment_id=self.experiment_id).values('key', 'value')
            values = {pair['key']: pair['value'] for pair in values}

            if not self._is_enrollment_inside_date_bounds(
                    values, user, course_key):
                return self._cache_bucket(experiment_name, 0)

        # Determine the user's bucket.
        # First check if forced into a particular bucket, using our subordinate bucket flags.
        # If not, calculate their default bucket using a consistent hash function.
        for i, bucket_flag in enumerate(self.bucket_flags):
            if bucket_flag.is_enabled(course_key):
                bucket = i
                break
        else:
            bucket = stable_bucketing_hash_group(bucketing_group_name,
                                                 self.num_buckets,
                                                 user.username)

        session_key = 'tracked.{}'.format(experiment_name)
        if (track and hasattr(request, 'session')
                and session_key not in request.session
                and not masquerading_as_specific_student):
            segment.track(user_id=user.id,
                          event_name='edx.bi.experiment.user.bucketed',
                          properties={
                              'site': request.site.domain,
                              'app_label': self.waffle_namespace.name,
                              'experiment': self.flag_name,
                              'course_id':
                              str(course_key) if course_key else None,
                              'bucket': bucket,
                              'is_staff': user.is_staff,
                              'nonInteraction': 1,
                          })

            # Mark that we've recorded this bucketing, so that we don't do it again this session
            request.session[session_key] = True

        return self._cache_bucket(experiment_name, bucket)
Ejemplo n.º 10
0
    def get_bucket(self, course_key=None, track=True):
        """
        Return which bucket number the specified user is in.

        Bucket 0 is assumed to be the control bucket and will be returned if the experiment is not enabled for
        this user and course.
        """
        # Keep some imports in here, because this class is commonly used at a module level, and we want to avoid
        # circular imports for any models.
        from experiments.models import ExperimentKeyValue

        request = get_current_request()
        if not request:
            return 0

        if not hasattr(request, 'user') or not request.user.id:
            # We need username for stable bucketing and id for tracking, so just skip anonymous (not-logged-in) users
            return 0

        # Use course key in experiment name to separate caches and segment calls per-course-run
        experiment_name = self.namespaced_flag_name + ('.{}'.format(course_key)
                                                       if course_key else '')

        # Check if we have a cache for this request already
        request_cache = RequestCache('experiments')
        cache_response = request_cache.get_cached_response(experiment_name)
        if cache_response.is_found:
            return cache_response.value

        # Check if the main flag is even enabled for this user and course.
        if not self.is_experiment_on(
                course_key):  # grabs user from the current request, if any
            return self._cache_bucket(experiment_name, 0)

        # Check if the enrollment should even be considered (if it started before the experiment wants, we ignore)
        if course_key and self.experiment_id is not None:
            values = ExperimentKeyValue.objects.filter(
                experiment_id=self.experiment_id).values('key', 'value')
            values = {pair['key']: pair['value'] for pair in values}

            if not self._is_enrollment_inside_date_bounds(
                    values, request.user, course_key):
                return self._cache_bucket(experiment_name, 0)

        bucket = stable_bucketing_hash_group(experiment_name, self.num_buckets,
                                             request.user.username)

        # Now check if the user is forced into a particular bucket, using our subordinate bucket flags
        for i, bucket_flag in enumerate(self.bucket_flags):
            if bucket_flag.is_enabled(course_key):
                bucket = i
                break

        session_key = 'tracked.{}'.format(experiment_name)
        if track and hasattr(request,
                             'session') and session_key not in request.session:
            segment.track(user_id=request.user.id,
                          event_name='edx.bi.experiment.user.bucketed',
                          properties={
                              'site': request.site.domain,
                              'app_label': self.waffle_namespace.name,
                              'experiment': self.flag_name,
                              'course_id':
                              str(course_key) if course_key else None,
                              'bucket': bucket,
                              'is_staff': request.user.is_staff,
                              'nonInteraction': 1,
                          })

            # Mark that we've recorded this bucketing, so that we don't do it again this session
            request.session[session_key] = True

        return self._cache_bucket(experiment_name, bucket)