示例#1
0
    def handle_goal(goal, today, sunday_date, monday_date):
        """Sends an email reminder for a single CourseGoal, if it passes all our checks"""
        if not COURSE_GOALS_NUMBER_OF_DAYS_GOALS.is_enabled(goal.course_key):
            return False

        enrollment = CourseEnrollment.get_enrollment(goal.user,
                                                     goal.course_key,
                                                     select_related=['course'])
        # If you're not actively enrolled in the course or your enrollment was this week
        if not enrollment or not enrollment.is_active or enrollment.created.date(
        ) >= monday_date:
            return False

        audit_access_expiration_date = get_user_course_expiration_date(
            goal.user, enrollment.course_overview)
        # If an audit user's access expires this week, exclude them from the email since they may not
        # be able to hit their goal anyway
        if audit_access_expiration_date and audit_access_expiration_date.date(
        ) <= sunday_date:
            return False

        cert = get_certificate_for_user_id(goal.user, goal.course_key)
        # If a user has a downloadable certificate, we will consider them as having completed
        # the course and opt them out of receiving emails
        if cert and cert.status == CertificateStatuses.downloadable:
            return False

        # Check the number of days left to successfully hit their goal
        week_activity_count = UserActivity.objects.filter(
            user=goal.user,
            course_key=goal.course_key,
            date__gte=monday_date,
        ).count()
        required_days_left = goal.days_per_week - week_activity_count
        # The weekdays are 0 indexed, but we want this to be 1 to match required_days_left.
        # Essentially, if today is Sunday, days_left_in_week should be 1 since they have Sunday to hit their goal.
        days_left_in_week = SUNDAY_WEEKDAY - today.weekday() + 1

        # We want to email users in the morning of their timezone
        user_timezone = get_user_timezone_or_last_seen_timezone_or_utc(
            goal.user)
        now_in_users_timezone = datetime.now(user_timezone)
        if not 9 <= now_in_users_timezone.hour < 12:
            return False

        if required_days_left == days_left_in_week:
            send_ace_message(goal)
            CourseGoalReminderStatus.objects.update_or_create(
                goal=goal, defaults={'email_reminder_sent': True})
            return True

        return False
示例#2
0
 def course_goals(self):
     """
     Returns a dict of course goals
     """
     if COURSE_GOALS_NUMBER_OF_DAYS_GOALS.is_enabled():
         course_goals = {
             'goal_options': [],
             'selected_goal': None
         }
         user_is_enrolled = CourseEnrollment.is_enrolled(self.effective_user, self.course_key)
         if (user_is_enrolled and ENABLE_COURSE_GOALS.is_enabled(self.course_key)):
             selected_goal = get_course_goal(self.effective_user, self.course_key)
             if selected_goal:
                 course_goals['selected_goal'] = {
                     'days_per_week': selected_goal.days_per_week,
                     'subscribed_to_reminders': selected_goal.subscribed_to_reminders,
                 }
         return course_goals
def save_course_goal(request):
    course_id = request.data.get('course_id')
    goal_key = request.data.get('goal_key')
    days_per_week = request.data.get('days_per_week')
    subscribed_to_reminders = request.data.get('subscribed_to_reminders')

    # If body doesn't contain 'course_id', return 400 to client.
    if not course_id:
        raise ParseError("'course_id' is required.")

    if COURSE_GOALS_NUMBER_OF_DAYS_GOALS.is_enabled():
        # If body doesn't contain the required goals fields, return 400 to client.
        if days_per_week is None or subscribed_to_reminders is None:
            raise ParseError(
                "'days_per_week' and 'subscribed_to_reminders' are required.")

        try:
            add_course_goal(request.user, course_id, days_per_week,
                            subscribed_to_reminders)
            return Response({
                'header':
                _('Your course goal has been successfully set.'),
                'message':
                _('Course goal updated successfully.'),
            })
        except Exception:
            raise UnableToSaveCourseGoal

    else:
        # If body doesn't contain 'goal', return 400 to client.
        if not goal_key:
            raise ParseError("'goal_key' is required.")

        try:
            add_course_goal_deprecated(request.user, course_id, goal_key)
            return Response({
                'header':
                _('Your course goal has been successfully set.'),
                'message':
                _('Course goal updated successfully.'),
            })
        except Exception:
            raise UnableToSaveCourseGoal
示例#4
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,
            )

        if not COURSE_GOALS_NUMBER_OF_DAYS_GOALS.is_enabled(course_key):
            log.warning(
                'For this mobile request, user activity is not enabled for this user {} and course {}'
                .format(str(user_id), str(course_key)))
            return Response(status=(200))

        # Populate user activity for tracking progress towards a user's course goals
        UserActivity.record_user_activity(user, course_key)
        return Response(status=(200))
示例#5
0
    def get(self, request, *args, **kwargs):  # pylint: disable=too-many-statements
        course_key_string = kwargs.get('course_key_string')
        course_key = CourseKey.from_string(course_key_string)
        course_usage_key = modulestore().make_course_usage_key(course_key)  # pylint: disable=unused-variable

        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)

        masquerade_object, request.user = setup_masquerade(
            request,
            course_key,
            staff_access=has_access(request.user, 'staff', course_key),
            reset_masquerade_data=True,
        )

        user_is_masquerading = is_masquerading(
            request.user, course_key, course_masquerade=masquerade_object)

        course_overview = get_course_overview_or_404(course_key)
        enrollment = CourseEnrollment.get_enrollment(request.user, course_key)
        enrollment_mode = getattr(enrollment, 'mode', None)
        allow_anonymous = COURSE_ENABLE_UNENROLLED_ACCESS_FLAG.is_enabled(
            course_key)
        allow_public = allow_anonymous and course.course_visibility == COURSE_VISIBILITY_PUBLIC
        allow_public_outline = allow_anonymous and course.course_visibility == COURSE_VISIBILITY_PUBLIC_OUTLINE

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

        if course_home_legacy_is_active(course.id):
            dates_tab_link = request.build_absolute_uri(
                reverse('dates', args=[course.id]))
        else:
            dates_tab_link = get_learning_mfe_home_url(course_key=course.id,
                                                       view_name='dates')

        # Set all of the defaults
        access_expiration = None
        cert_data = None
        course_blocks = None
        course_goals = {'goal_options': [], 'selected_goal': None}
        course_tools = CourseToolsPluginManager.get_enabled_course_tools(
            request, course_key)
        dates_widget = {
            'course_date_blocks': [],
            'dates_tab_link': dates_tab_link,
            'user_timezone': user_timezone,
        }
        enroll_alert = {
            'can_enroll': True,
            'extra_text': None,
        }
        handouts_html = None
        offer_data = None
        resume_course = {
            'has_visited_course': False,
            'url': None,
        }
        welcome_message_html = None

        is_enrolled = enrollment and enrollment.is_active
        is_staff = bool(has_access(request.user, 'staff', course_key))
        show_enrolled = is_enrolled or is_staff
        if show_enrolled:
            course_blocks = get_course_outline_block_tree(
                request, course_key_string, request.user)
            date_blocks = get_course_date_blocks(course,
                                                 request.user,
                                                 request,
                                                 num_assignments=1)
            dates_widget['course_date_blocks'] = [
                block for block in date_blocks
                if not isinstance(block, TodaysDate)
            ]

            handouts_html = get_course_info_section(request, request.user,
                                                    course, 'handouts')
            welcome_message_html = get_current_update_for_user(request, course)

            offer_data = generate_offer_data(request.user, course_overview)
            access_expiration = get_access_expiration_data(
                request.user, course_overview)
            cert_data = get_cert_data(request.user, course,
                                      enrollment.mode) if is_enrolled else None

            if COURSE_GOALS_NUMBER_OF_DAYS_GOALS.is_enabled():
                if (is_enrolled
                        and ENABLE_COURSE_GOALS.is_enabled(course_key)):

                    course_goals = {
                        'selected_goal': None,
                        'weekly_learning_goal_enabled': True,
                    }
                    selected_goal = get_course_goal(request.user, course_key)
                    if selected_goal:
                        course_goals['selected_goal'] = {
                            'days_per_week':
                            selected_goal.days_per_week,
                            'subscribed_to_reminders':
                            selected_goal.subscribed_to_reminders,
                        }
            else:
                # Only show the set course goal message for enrolled, unverified
                # users in a course that allows for verified statuses.
                is_already_verified = CourseEnrollment.is_enrolled_as_verified(
                    request.user, course_key)
                if not is_already_verified and has_course_goal_permission(
                        request, course_key_string,
                    {'is_enrolled': is_enrolled}):
                    course_goals = {
                        'goal_options':
                        valid_course_goals_ordered(include_unsure=True),
                        'selected_goal':
                        None
                    }

                    selected_goal = get_course_goal(request.user, course_key)
                    if selected_goal:
                        course_goals['selected_goal'] = {
                            'key': selected_goal.goal_key,
                            'text':
                            get_course_goal_text(selected_goal.goal_key),
                        }

            try:
                resume_block = get_key_to_last_completed_block(
                    request.user, course.id)
                resume_course['has_visited_course'] = True
                resume_path = reverse('jump_to',
                                      kwargs={
                                          'course_id': course_key_string,
                                          'location': str(resume_block)
                                      })
                resume_course['url'] = request.build_absolute_uri(resume_path)
            except UnavailableCompletionData:
                start_block = get_start_block(course_blocks)
                resume_course['url'] = start_block['lms_web_url']

        elif allow_public_outline or allow_public or user_is_masquerading:
            course_blocks = get_course_outline_block_tree(
                request, course_key_string, None)
            if allow_public or user_is_masquerading:
                handouts_html = get_course_info_section(
                    request, request.user, course, 'handouts')

        if not show_enrolled:
            if CourseMode.is_masters_only(course_key):
                enroll_alert['can_enroll'] = False
                enroll_alert['extra_text'] = _(
                    'Please contact your degree administrator or '
                    '{platform_name} Support if you have questions.').format(
                        platform_name=settings.PLATFORM_NAME)
            elif course.invitation_only:
                enroll_alert['can_enroll'] = False

        # Sometimes there are sequences returned by Course Blocks that we
        # don't actually want to show to the user, such as when a sequence is
        # composed entirely of units that the user can't access. The Learning
        # Sequences API knows how to roll this up, so we use it determine which
        # sequences we should remove from course_blocks.
        #
        # The long term goal is to remove the Course Blocks API call entirely,
        # so this is a tiny first step in that migration.
        if course_blocks and learning_sequences_api_available(course_key):
            user_course_outline = get_user_course_outline(
                course_key, request.user, datetime.now(tz=timezone.utc))
            available_seq_ids = {
                str(usage_key)
                for usage_key in user_course_outline.sequences
            }

            # course_blocks is a reference to the root of the course, so we go
            # through the chapters (sections) to look for sequences to remove.
            for chapter_data in course_blocks['children']:
                chapter_data['children'] = [
                    seq_data for seq_data in chapter_data['children'] if
                    (seq_data['id'] in available_seq_ids or
                     # Edge case: Sometimes we have weird course structures.
                     # We expect only sequentials here, but if there is
                     # another type, just skip it (don't filter it out).
                     seq_data['type'] != 'sequential')
                ] if 'children' in chapter_data else []

        user_has_passing_grade = False
        if not request.user.is_anonymous:
            user_grade = CourseGradeFactory().read(request.user, course)
            if user_grade:
                user_has_passing_grade = user_grade.passed

        data = {
            'access_expiration': access_expiration,
            'cert_data': cert_data,
            'course_blocks': course_blocks,
            'course_goals': course_goals,
            'course_tools': course_tools,
            'dates_widget': dates_widget,
            'enroll_alert': enroll_alert,
            'enrollment_mode': enrollment_mode,
            'handouts_html': handouts_html,
            'has_ended': course.has_ended(),
            'offer': offer_data,
            'resume_course': resume_course,
            'user_has_passing_grade': user_has_passing_grade,
            'welcome_message_html': welcome_message_html,
        }
        context = self.get_serializer_context()
        context['course_overview'] = course_overview
        context['enable_links'] = show_enrolled or allow_public
        context['enrollment'] = enrollment
        serializer = self.get_serializer_class()(data, context=context)

        return Response(serializer.data)
示例#6
0
    def record_user_activity(cls,
                             user,
                             course_key,
                             request=None,
                             only_if_mobile_app=False):
        '''
        Update the user activity table with a record for this activity.

        Since we store one activity per date, we don't need to query the database
        for every activity on a given date.
        To avoid unnecessary queries, we store a record in a cache once we have an activity for the date,
        which times out at the end of that date (in the user's timezone).

        The request argument is only used to check if the request is coming from a mobile app.
        Once the only_if_mobile_app argument is removed the request argument can be removed as well.

        The return value is the id of the object that was created, or retrieved.
        A return value of None signifies that a user activity record was not stored or retrieved
        '''
        if not COURSE_GOALS_NUMBER_OF_DAYS_GOALS.is_enabled(course_key):
            return None

        if not (user and user.id) or not course_key:
            return None

        if only_if_mobile_app and request and not is_request_from_mobile_app(
                request):
            return None

        if is_masquerading(user, course_key):
            return None

        user_preferences = get_user_preferences(user)
        timezone = pytz.timezone(user_preferences.get('time_zone', 'UTC'))
        now = datetime.now(timezone)
        date = now.date()

        cache_key = 'goals_user_activity_{}_{}_{}'.format(
            str(user.id), str(course_key), str(date))

        cached_value = TieredCache.get_cached_response(cache_key)
        if cached_value.is_found:
            # Temporary debugging log for testing mobile app connection
            if request:
                log.info(
                    'Retrieved cached value with request {} for user and course combination {} {}'
                    .format(str(request.build_absolute_uri()), str(user.id),
                            str(course_key)))
            return cached_value.value, False

        activity_object, __ = cls.objects.get_or_create(user=user,
                                                        course_key=course_key,
                                                        date=date)

        # Cache result until the end of the day to avoid unnecessary database requests
        tomorrow = now + timedelta(days=1)
        midnight = datetime(year=tomorrow.year,
                            month=tomorrow.month,
                            day=tomorrow.day,
                            hour=0,
                            minute=0,
                            second=0,
                            tzinfo=timezone)
        seconds_until_midnight = (midnight - now).seconds

        TieredCache.set_all_tiers(cache_key, activity_object.id,
                                  seconds_until_midnight)
        # Temporary debugging log for testing mobile app connection
        if request:
            log.info(
                'Set cached value with request {} for user and course combination {} {}'
                .format(str(request.build_absolute_uri()), str(user.id),
                        str(course_key)))
        return activity_object.id