예제 #1
0
 def _wrapper(self, request, *args, **kwargs):
     """
     Expects kwargs to contain 'course_id'.
     Passes the course descriptor to the given decorated function.
     Raises 404 if access to course is disallowed.
     """
     course_id = CourseKey.from_string(kwargs.pop('course_id'))
     with modulestore().bulk_operations(course_id):
         try:
             course = get_course_with_access(
                 request.user,
                 'load_mobile',
                 course_id,
                 depth=depth,
                 check_if_enrolled=True,
             )
             # Record user activity for tracking progress towards a user's course goals (for mobile app)
             UserActivity.record_user_activity(
                 request.user, course_id, request=request, only_if_mobile_app=True
             )
         except CoursewareAccessException as error:
             return Response(data=error.to_json(), status=status.HTTP_404_NOT_FOUND)
         except CourseAccessRedirect as error:
             # If the redirect contains information about the triggering AccessError,
             # return the information contained in the AccessError.
             if error.access_error is not None:
                 return Response(data=error.access_error.to_json(), status=status.HTTP_404_NOT_FOUND)
             # Raise a 404 if the user does not have course access
             raise Http404  # lint-amnesty, pylint: disable=raise-missing-from
         return func(self, request, course=course, *args, **kwargs)
예제 #2
0
    def list(self, request):
        """
        Implements the GET method for the list endpoint as described in the
        class docstring.
        """
        form = ThreadListGetForm(request.GET)
        if not form.is_valid():
            raise ValidationError(form.errors)

        # Record user activity for tracking progress towards a user's course goals (for mobile app)
        UserActivity.record_user_activity(
            request.user, form.cleaned_data["course_id"], request=request, only_if_mobile_app=True
        )

        return get_thread_list(
            request,
            form.cleaned_data["course_id"],
            form.cleaned_data["page"],
            form.cleaned_data["page_size"],
            form.cleaned_data["topic_id"],
            form.cleaned_data["text_search"],
            form.cleaned_data["following"],
            form.cleaned_data["author"],
            form.cleaned_data["thread_type"],
            form.cleaned_data["flagged"],
            form.cleaned_data["view"],
            form.cleaned_data["order_by"],
            form.cleaned_data["order_direction"],
            form.cleaned_data["requested_fields"],
            form.cleaned_data["count_flagged"],
        )
예제 #3
0
    def post(self, request, *args, **kwargs):
        """
        Handle the POST request

        Populate the user activity table.
        """
        user_id = request.data.get('user_id')
        course_key = request.data.get('course_key')

        if not user_id or not course_key:
            return Response(
                'User id and course key are required',
                status=status.HTTP_400_BAD_REQUEST,
            )

        try:
            user_id = int(user_id)
            user = User.objects.get(id=user_id)
        except User.DoesNotExist:
            return Response(
                'Provided user id does not correspond to an existing user',
                status=status.HTTP_400_BAD_REQUEST,
            )

        try:
            course_key = CourseKey.from_string(course_key)
        except InvalidKeyError:
            return Response(
                'Provided course key is not valid',
                status=status.HTTP_400_BAD_REQUEST,
            )

        # Populate user activity for tracking progress towards a user's course goals
        UserActivity.record_user_activity(user, course_key)
        return Response(status=(200))
예제 #4
0
    def list(self, request, hide_access_denials=False):  # pylint: disable=arguments-differ
        """
        Retrieves the usage_key for the requested course, and then returns the
        same information that would be returned by BlocksView.list, called with
        that usage key

        Arguments:
            request - Django request object
        """

        # convert the requested course_key to the course's root block's usage_key
        course_key_string = request.query_params.get('course_id', None)
        if not course_key_string:
            raise ValidationError('course_id is required.')

        try:
            course_key = CourseKey.from_string(course_key_string)
            course_usage_key = modulestore().make_course_usage_key(course_key)
        except InvalidKeyError:
            raise ValidationError(
                f"'{str(course_key_string)}' is not a valid course key.")  # lint-amnesty, pylint: disable=raise-missing-from
        response = super().list(request,
                                course_usage_key,
                                hide_access_denials=hide_access_denials)

        if RECORD_USER_ACTIVITY_FLAG.is_enabled():
            # Record user activity for tracking progress towards a user's course goals (for mobile app)
            UserActivity.record_user_activity(request.user,
                                              course_key,
                                              request=request,
                                              only_if_mobile_app=True)

        calculate_completion = any(
            'completion' in param
            for param in request.query_params.getlist('requested_fields', []))
        if not calculate_completion:
            return response

        course_blocks = {}
        root = None
        if request.query_params.get('return_type') == 'list':
            for course_block in response.data:
                course_blocks[course_block['id']] = course_block

                if course_block.get('type') == 'course':
                    root = course_block['id']
        else:
            root = response.data['root']
            course_blocks = response.data['blocks']

        if not root:
            raise ValueError(
                f"Unable to find course block in {course_key_string}")

        recurse_mark_complete(root, course_blocks)
        return response
예제 #5
0
 def get(self, request, course_id):
     """Implements the GET method as described in the class docstring."""
     course_key = CourseKey.from_string(
         course_id)  # TODO: which class is right?
     if RECORD_USER_ACTIVITY_FLAG.is_enabled():
         # Record user activity for tracking progress towards a user's course goals (for mobile app)
         UserActivity.record_user_activity(request.user,
                                           course_key,
                                           request=request,
                                           only_if_mobile_app=True)
     return Response(get_course(request, course_key))
예제 #6
0
    def get(self, request, course_id):
        """
        Retrieve general discussion metadata for a course.

        **Example Requests**:

            GET /api/discussion/v1/courses/course-v1:ExampleX+Subject101+2015
        """
        course_key = CourseKey.from_string(course_id)  # TODO: which class is right?
        # Record user activity for tracking progress towards a user's course goals (for mobile app)
        UserActivity.record_user_activity(request.user, course_key, request=request, only_if_mobile_app=True)
        return Response(get_course(request, course_key))
예제 #7
0
    def get(self, request, *args, **kwargs):
        course_key_string = kwargs.get('course_key_string')
        course_key = CourseKey.from_string(course_key_string)
        # Although this course data is not used this method will return 404 if course does not exist
        get_course_with_access(request.user, 'load', course_key)

        # Record user activity for tracking progress towards a user's course goals (for mobile app)
        UserActivity.record_user_activity(request.user,
                                          course_key,
                                          request=request,
                                          only_if_mobile_app=True)

        serializer = self.get_serializer({})
        return Response(serializer.data)
예제 #8
0
    def test_when_record_user_activity_does_not_perform_updates(self):
        '''
        Ensure that record user activity is not called when:
            1. user or course are not defined
            2. we have already recorded user activity for this user/course on this date
            and have a record in the cache
        '''
        with patch.object(
                TieredCache, 'set_all_tiers',
                wraps=TieredCache.set_all_tiers) as activity_cache_set:
            UserActivity.record_user_activity(self.user, None)
            activity_cache_set.assert_not_called()

            UserActivity.record_user_activity(None, self.course.id)
            activity_cache_set.assert_not_called()

        cache_key = 'goals_user_activity_{}_{}_{}'.format(
            str(self.user.id), str(self.course.id), str(datetime.now().date()))
        TieredCache.set_all_tiers(cache_key, 'test', 3600)

        with patch.object(
                TieredCache, 'set_all_tiers',
                wraps=TieredCache.set_all_tiers) as activity_cache_set:
            UserActivity.record_user_activity(self.user, self.course.id)
            activity_cache_set.assert_not_called()

            # Test that the happy path works to ensure that the measurement in this test isn't broken
            user2 = UserFactory()
            UserActivity.record_user_activity(user2, self.course.id)
            activity_cache_set.assert_called_once()
예제 #9
0
    def get(self, request, *args, **kwargs):
        course_key_string = kwargs.get('course_key_string')
        course_key = CourseKey.from_string(course_key_string)

        if course_home_legacy_is_active(course_key):
            raise Http404

        # Enable NR tracing for this view based on course
        monitoring_utils.set_custom_attribute('course_id', course_key_string)
        monitoring_utils.set_custom_attribute('user_id', request.user.id)
        monitoring_utils.set_custom_attribute('is_staff', request.user.is_staff)

        course = get_course_with_access(request.user, 'load', course_key, check_if_enrolled=False)
        is_staff = bool(has_access(request.user, 'staff', course_key))

        _, request.user = setup_masquerade(
            request,
            course_key,
            staff_access=is_staff,
            reset_masquerade_data=True,
        )

        # Record user activity for tracking progress towards a user's course goals (for mobile app)
        UserActivity.record_user_activity(request.user, course.id, request=request, only_if_mobile_app=True)

        if not CourseEnrollment.is_enrolled(request.user, course_key) and not is_staff:
            return Response('User not enrolled.', status=401)

        blocks = get_course_date_blocks(course, request.user, request, include_access=True, include_past_dates=True)

        learner_is_full_access = not ContentTypeGatingConfig.enabled_for_enrollment(
            user=request.user,
            course_key=course_key,
        )

        # User locale settings
        user_timezone_locale = user_timezone_locale_prefs(request)
        user_timezone = user_timezone_locale['user_timezone']

        data = {
            'has_ended': course.has_ended(),
            'course_date_blocks': [block for block in blocks if not isinstance(block, TodaysDate)],
            'learner_is_full_access': learner_is_full_access,
            'user_timezone': user_timezone,
        }
        context = self.get_serializer_context()
        context['learner_is_full_access'] = learner_is_full_access
        serializer = self.get_serializer_class()(data, context=context)

        return Response(serializer.data)
예제 #10
0
 def get(self, request, course_id):
     """
     Implements the GET method as described in the class docstring.
     """
     course_key = CourseKey.from_string(course_id)
     topic_ids = self.request.GET.get('topic_id')
     with modulestore().bulk_operations(course_key):
         response = get_course_topics(
             request,
             course_key,
             set(topic_ids.strip(',').split(',')) if topic_ids else None,
         )
         # Record user activity for tracking progress towards a user's course goals (for mobile app)
         UserActivity.record_user_activity(request.user, course_key, request=request, only_if_mobile_app=True)
     return Response(response)
예제 #11
0
    def test_masquerading(self):
        '''
        Method only records activity if the user is not masquerading
        '''
        with patch.object(
                TieredCache, 'set_all_tiers',
                wraps=TieredCache.set_all_tiers) as activity_cache_set:
            UserActivity.record_user_activity(self.user, self.course.id)
            activity_cache_set.assert_called_once()

        with patch.object(
                TieredCache, 'set_all_tiers',
                wraps=TieredCache.set_all_tiers) as activity_cache_set:
            with patch('lms.djangoapps.course_goals.models.is_masquerading',
                       return_value=True):
                UserActivity.record_user_activity(self.user, self.course.id)
                activity_cache_set.assert_not_called()
예제 #12
0
    def get_object(self):
        """
        Return the requested course object, if the user has appropriate
        permissions.
        """
        if self.request.user.is_staff:
            username = self.request.GET.get('username',
                                            '') or self.request.user.username
        else:
            username = self.request.user.username
        course_key = CourseKey.from_string(self.kwargs['course_key_string'])
        overview = CoursewareMeta(
            course_key,
            self.request,
            username=username,
        )
        # Record course goals user activity for learning mfe courseware on web
        if RECORD_USER_ACTIVITY_FLAG.is_enabled():
            UserActivity.record_user_activity(self.request.user, course_key)

        return overview
예제 #13
0
    def test_mobile_argument(self):
        '''
        Method only records activity if the request is coming from the mobile app
        when the only_if_mobile_app argument is true
        '''
        with patch.object(
                TieredCache, 'set_all_tiers',
                wraps=TieredCache.set_all_tiers) as activity_cache_set:
            UserActivity.record_user_activity(self.user,
                                              self.course.id,
                                              request=self.request,
                                              only_if_mobile_app=True)
            activity_cache_set.assert_not_called()

            with patch(
                    'lms.djangoapps.course_goals.models.is_request_from_mobile_app',
                    return_value=True):
                UserActivity.record_user_activity(self.user,
                                                  self.course.id,
                                                  request=self.request,
                                                  only_if_mobile_app=True)
                activity_cache_set.assert_called_once()
예제 #14
0
    def get_object(self):
        """
        Return the requested course object, if the user has appropriate
        permissions.
        """
        original_user = self.request.user
        if self.request.user.is_staff:
            username = self.request.GET.get('username', '') or self.request.user.username
        else:
            username = self.request.user.username
        course_key = CourseKey.from_string(self.kwargs['course_key_string'])
        overview = CoursewareMeta(
            course_key,
            self.request,
            username=username,
        )
        # Record course goals user activity for learning mfe courseware on web
        UserActivity.record_user_activity(self.request.user, course_key)

        # Record a user's browser timezone
        self.set_last_seen_courseware_timezone(original_user)

        return overview
예제 #15
0
    def test_that_user_activity_cache_works_properly(self):
        '''
        Ensure that the cache for user activity works properly
            1. user or course are not defined
            2. we have already recorded user activity for this user/course on this date
            and have a record in the cache
        '''
        with patch.object(
                TieredCache, 'set_all_tiers',
                wraps=TieredCache.set_all_tiers) as activity_cache_set:
            UserActivity.record_user_activity(self.user, self.course.id)
            activity_cache_set.assert_called_once()

        with patch.object(
                TieredCache, 'set_all_tiers',
                wraps=TieredCache.set_all_tiers) as activity_cache_set:
            UserActivity.record_user_activity(self.user, self.course.id)
            activity_cache_set.assert_not_called()

            now_plus_1_day = datetime.now() + timedelta(days=1)
            with freeze_time(now_plus_1_day):
                UserActivity.record_user_activity(self.user, self.course.id)
                activity_cache_set.assert_called_once()
예제 #16
0
    def get(self, request, *args, **kwargs):
        course_key_string = kwargs.get('course_key_string')
        course_key = CourseKey.from_string(course_key_string)
        original_user_is_global_staff = self.request.user.is_staff
        original_user_is_staff = has_access(request.user, 'staff',
                                            course_key).has_access

        course = course_detail(request, request.user.username, course_key)

        # We must compute course load access *before* setting up masquerading,
        # else course staff (who are not enrolled) will not be able view
        # their course from the perspective of a learner.
        load_access = check_course_access(
            course,
            request.user,
            'load',
            check_if_enrolled=True,
            check_if_authenticated=True,
        )

        _, request.user = setup_masquerade(
            request,
            course_key,
            staff_access=original_user_is_staff,
            reset_masquerade_data=True,
        )

        username = request.user.username if request.user.username else None
        enrollment = CourseEnrollment.get_enrollment(request.user,
                                                     course_key_string)
        user_is_enrolled = bool(enrollment and enrollment.is_active)

        can_load_courseware = courseware_mfe_is_visible(
            course_key=course_key,
            is_global_staff=original_user_is_global_staff,
            is_course_staff=original_user_is_staff)

        # User locale settings
        user_timezone_locale = user_timezone_locale_prefs(request)
        user_timezone = user_timezone_locale['user_timezone']

        browser_timezone = self.request.query_params.get(
            'browser_timezone', None)
        celebrations = get_celebrations_dict(
            request.user, enrollment, course,
            user_timezone if not None else browser_timezone)

        # Record course goals user activity for (web) learning mfe course tabs
        UserActivity.record_user_activity(request.user, course_key)

        data = {
            'course_id': course.id,
            'username': username,
            'is_staff': has_access(request.user, 'staff',
                                   course_key).has_access,
            'original_user_is_staff': original_user_is_staff,
            'number': course.display_number_with_default,
            'org': course.display_org_with_default,
            'start': course.start,
            'tabs': get_course_tab_list(request.user, course),
            'title': course.display_name_with_default,
            'is_self_paced': getattr(course, 'self_paced', False),
            'is_enrolled': user_is_enrolled,
            'course_access': load_access.to_json(),
            'can_load_courseware': can_load_courseware,
            'celebrations': celebrations,
            'user_timezone': user_timezone,
        }
        context = self.get_serializer_context()
        context['course'] = course
        context['course_overview'] = course
        context['enrollment'] = enrollment
        serializer = self.get_serializer_class()(data, context=context)
        return Response(serializer.data)