<commit_before>"""
Learner analytics dashboard views
"""
import logging
import math
import urllib
from datetime import datetime, timedelta

import pytz
import requests
from analyticsclient.client import Client
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.core.cache import cache
from django.core.urlresolvers import reverse
from django.http import Http404
from django.shortcuts import render_to_response
from django.utils.decorators import method_decorator
from django.views.decorators.cache import cache_control
from django.views.generic import View
from opaque_keys.edx.keys import CourseKey
from student.models import CourseEnrollment
from util.views import ensure_valid_course_key
from xmodule.modulestore.django import modulestore

from course_modes.models import get_cosmetic_verified_display_price
from lms.djangoapps.course_api.blocks.api import get_blocks
from lms.djangoapps.commerce.utils import EcommerceService
from lms.djangoapps.courseware.courses import get_course_with_access
from lms.djangoapps.discussion.views import create_user_profile_context
from lms.djangoapps.grades.course_grade_factory import CourseGradeFactory
from lms.lib.comment_client.utils import CommentClient500Error
from openedx.features.course_experience import default_course_url_name
from openedx.core.djangoapps.user_api.accounts.image_helpers import get_profile_image_urls_for_user

from . import ENABLE_DASHBOARD_TAB

log = logging.getLogger(__name__)


class LearnerAnalyticsView(View):
    """
    Displays the Learner Analytics Dashboard.
    """
    def __init__(self):
        View.__init__(self)
        self.analytics_client = Client(base_url=settings.ANALYTICS_API_URL, auth_token=settings.ANALYTICS_API_KEY)

    @method_decorator(login_required)
    @method_decorator(cache_control(no_cache=True, no_store=True, must_revalidate=True))
    @method_decorator(ensure_valid_course_key)
    def get(self, request, course_id):
        """
        Displays the user's Learner Analytics for the specified course.

        Arguments:
            request: HTTP request
            course_id (unicode): course id
        """
        course_key = CourseKey.from_string(course_id)
        if not ENABLE_DASHBOARD_TAB.is_enabled(course_key):
            raise Http404

        course = get_course_with_access(request.user, 'load', course_key, check_if_enrolled=True)
        course_url_name = default_course_url_name(course.id)
        course_url = reverse(course_url_name, kwargs={'course_id': unicode(course.id)})

        is_verified = CourseEnrollment.is_enrolled_as_verified(request.user, course_key)
        has_access = is_verified or request.user.is_staff

        enrollment = CourseEnrollment.get_enrollment(request.user, course_key)

        upgrade_price = None
        upgrade_url = None

        if enrollment and enrollment.upgrade_deadline:
            upgrade_url = EcommerceService().upgrade_url(request.user, course_key)
            upgrade_price = get_cosmetic_verified_display_price(course)

        context = {
            'upgrade_price': upgrade_price,
            'upgrade_link': upgrade_url,
            'course': course,
            'course_url': course_url,
            'disable_courseware_js': True,
            'uses_pattern_library': True,
            'is_self_paced': course.self_paced,
            'is_verified': is_verified,
            'has_access': has_access,
        }

        if (has_access):
            grading_policy = course.grading_policy

            (raw_grade_data, answered_percent, percent_grade) = self.get_grade_data(request.user, course_key, grading_policy['GRADE_CUTOFFS'])
            raw_schedule_data = self.get_assignments_with_due_date(request, course_key)

            # TODO: LEARNER-3854: Removed assignment schedule and grade sorting code.
            #  The original code had several problems:
            #    - It dropped one of the surveys from course-v1:Microsoft+DAT206x+1T2018.
            #    - It was dependent on due dates which are not available for self-paced yet.
            grade_data = raw_grade_data
            schedule_data = []

            # TODO: LEARNER-3854: Fix hacked defaults with real error handling if implementing Learner Analytics.
            try:
                weekly_active_users = self.get_weekly_course_activity_count(course_key)
                week_streak = self.consecutive_weeks_of_course_activity_for_user(
                    request.user.username, course_key
                )
            except Exception as e:
                logging.exception(e)
                weekly_active_users = 134
                week_streak = 1

            context.update({
                'grading_policy': grading_policy,
                'assignment_grades': grade_data,
                'answered_percent': answered_percent,
                'assignment_schedule': schedule_data,
                'assignment_schedule_raw': raw_schedule_data,
                'profile_image_urls': get_profile_image_urls_for_user(request.user, request),
                'discussion_info': self.get_discussion_data(request, course_key),
                'passing_grade': math.ceil(100 * course.lowest_passing_grade),
                'percent_grade': math.ceil(100 * percent_grade),
                'weekly_active_users': weekly_active_users,
                'week_streak': week_streak,
            })

        return render_to_response('learner_analytics/dashboard.html', context)

    def get_grade_data(self, user, course_key, grade_cutoffs):
        """
        Collects and formats the grades data for a particular user and course.

        Args:
            user (User)
            course_key (CourseKey)
            grade_cutoffs: # TODO: LEARNER-3854: Complete docstring if implementing Learner Analytics.
        """
        course_grade = CourseGradeFactory().read(user, course_key=course_key)
        grades = []
        total_earned = 0
        total_possible = 0
        # answered_percent seems to be unused and it does not take into account assignment type weightings
        answered_percent = None
        for (location, subsection_grade) in course_grade.subsection_grades.iteritems():
            if subsection_grade.format is not None:
                possible = subsection_grade.graded_total.possible
                earned = subsection_grade.graded_total.earned
                passing_grade = math.ceil(possible * grade_cutoffs['Pass'])
                grades.append({
                    'assignment_type': subsection_grade.format,
                    'total_earned': earned,
                    'total_possible': possible,
                    'passing_grade': passing_grade,
                    'location': unicode(location),
                    'assigment_url': reverse('jump_to_id', kwargs={
                        'course_id': unicode(course_key),
                        'module_id': unicode(location),
                    })
                })
                if earned > 0:
                    total_earned += earned
                    total_possible += possible

        if total_possible > 0:
            answered_percent = float(total_earned) / total_possible
        return (grades, answered_percent, course_grade.percent)

    def sort_grade_and_schedule_data(self, grade_data, schedule_data):
        """
        Sort the assignments in grade_data and schedule_data to be in the same order.
        """
        schedule_dict = {assignment['location']: assignment for assignment in schedule_data}

        sorted_schedule_data = []
        sorted_grade_data = []
        for grade in grade_data:<commit_msg>Use the same ordering for LA dashboard as progress page.<commit_after>"""
Learner analytics dashboard views
"""
import logging
import math
import urllib
from datetime import datetime, timedelta

import pytz
import requests
from analyticsclient.client import Client
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.core.cache import cache
from django.core.urlresolvers import reverse
from django.http import Http404
from django.shortcuts import render_to_response
from django.utils.decorators import method_decorator
from django.views.decorators.cache import cache_control
from django.views.generic import View
from opaque_keys.edx.keys import CourseKey
from student.models import CourseEnrollment
from util.views import ensure_valid_course_key
from xmodule.modulestore.django import modulestore

from course_modes.models import get_cosmetic_verified_display_price
from lms.djangoapps.course_api.blocks.api import get_blocks
from lms.djangoapps.commerce.utils import EcommerceService
from lms.djangoapps.courseware.courses import get_course_with_access
from lms.djangoapps.discussion.views import create_user_profile_context
from lms.djangoapps.grades.course_grade_factory import CourseGradeFactory
from lms.lib.comment_client.utils import CommentClient500Error
from openedx.features.course_experience import default_course_url_name
from openedx.core.djangoapps.user_api.accounts.image_helpers import get_profile_image_urls_for_user

from . import ENABLE_DASHBOARD_TAB

log = logging.getLogger(__name__)


class LearnerAnalyticsView(View):
    """
    Displays the Learner Analytics Dashboard.
    """
    def __init__(self):
        View.__init__(self)
        self.analytics_client = Client(base_url=settings.ANALYTICS_API_URL, auth_token=settings.ANALYTICS_API_KEY)

    @method_decorator(login_required)
    @method_decorator(cache_control(no_cache=True, no_store=True, must_revalidate=True))
    @method_decorator(ensure_valid_course_key)
    def get(self, request, course_id):
        """
        Displays the user's Learner Analytics for the specified course.

        Arguments:
            request: HTTP request
            course_id (unicode): course id
        """
        course_key = CourseKey.from_string(course_id)
        if not ENABLE_DASHBOARD_TAB.is_enabled(course_key):
            raise Http404

        course = get_course_with_access(request.user, 'load', course_key, check_if_enrolled=True)
        course_url_name = default_course_url_name(course.id)
        course_url = reverse(course_url_name, kwargs={'course_id': unicode(course.id)})

        is_verified = CourseEnrollment.is_enrolled_as_verified(request.user, course_key)
        has_access = is_verified or request.user.is_staff

        enrollment = CourseEnrollment.get_enrollment(request.user, course_key)

        upgrade_price = None
        upgrade_url = None

        if enrollment and enrollment.upgrade_deadline:
            upgrade_url = EcommerceService().upgrade_url(request.user, course_key)
            upgrade_price = get_cosmetic_verified_display_price(course)

        context = {
            'upgrade_price': upgrade_price,
            'upgrade_link': upgrade_url,
            'course': course,
            'course_url': course_url,
            'disable_courseware_js': True,
            'uses_pattern_library': True,
            'is_self_paced': course.self_paced,
            'is_verified': is_verified,
            'has_access': has_access,
        }

        if (has_access):
            grading_policy = course.grading_policy

            (raw_grade_data, answered_percent, percent_grade) = self.get_grade_data(request.user, course_key, grading_policy['GRADE_CUTOFFS'])
            raw_schedule_data = self.get_assignments_with_due_date(request, course_key)

            grade_data, schedule_data = self.sort_grade_and_schedule_data(raw_grade_data, raw_schedule_data)

            # TODO: LEARNER-3854: Fix hacked defaults with real error handling if implementing Learner Analytics.
            try:
                weekly_active_users = self.get_weekly_course_activity_count(course_key)
                week_streak = self.consecutive_weeks_of_course_activity_for_user(
                    request.user.username, course_key
                )
            except Exception as e:
                logging.exception(e)
                weekly_active_users = 134
                week_streak = 1

            context.update({
                'grading_policy': grading_policy,
                'assignment_grades': grade_data,
                'answered_percent': answered_percent,
                'assignment_schedule': schedule_data,
                'assignment_schedule_raw': raw_schedule_data,
                'profile_image_urls': get_profile_image_urls_for_user(request.user, request),
                'discussion_info': self.get_discussion_data(request, course_key),
                'passing_grade': math.ceil(100 * course.lowest_passing_grade),
                'percent_grade': math.ceil(100 * percent_grade),
                'weekly_active_users': weekly_active_users,
                'week_streak': week_streak,
            })

        return render_to_response('learner_analytics/dashboard.html', context)

    def get_grade_data(self, user, course_key, grade_cutoffs):
        """
        Collects and formats the grades data for a particular user and course.

        Args:
            user (User)
            course_key (CourseKey)
            grade_cutoffs: # TODO: LEARNER-3854: Complete docstring if implementing Learner Analytics.
        """
        course_grade = CourseGradeFactory().read(user, course_key=course_key)
        grades = []
        total_earned = 0
        total_possible = 0
        # answered_percent seems to be unused and it does not take into account assignment type weightings
        answered_percent = None

        chapter_grades = course_grade.chapter_grades.values()

        for chapter in chapter_grades:
            # Note: this code exists on the progress page. We should be able to remove it going forward.
            if not chapter['display_name'] == "hidden":
                for subsection_grade in chapter['sections']:
                    log.info(subsection_grade.display_name)
                    possible = subsection_grade.graded_total.possible
                    earned = subsection_grade.graded_total.earned
                    passing_grade = math.ceil(possible * grade_cutoffs['Pass'])
                    grades.append({
                        'assignment_type': subsection_grade.format,
                        'total_earned': earned,
                        'total_possible': possible,
                        'passing_grade': passing_grade,
                        'display_name': subsection_grade.display_name,
                        'location': unicode(subsection_grade.location),
                        'assigment_url': reverse('jump_to_id', kwargs={
                            'course_id': unicode(course_key),
                            'module_id': unicode(subsection_grade.location),
                        })
                    })
                    if earned > 0:
                        total_earned += earned
                        total_possible += possible

        if total_possible > 0:
            answered_percent = float(total_earned) / total_possible
        return (grades, answered_percent, course_grade.percent)

    def sort_grade_and_schedule_data(self, grade_data, schedule_data):
        """
        Sort the assignments in grade_data and schedule_data to be in the same order.
        """
        schedule_dict = {assignment['location']: assignment for assignment in schedule_data}

        sorted_schedule_data = []
        sorted_grade_data = []
        for grade in grade_data:
{'fraction_non_alphanumeric': 0.06933057904589762, 'fraction_numerical': 0.004841520883093409, 'mean_word_length': 2.6806842480399147, 'pattern_counts': {'":': 1, '<': 3, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5385172', 'n_tokens_mistral': 4319, 'n_tokens_neox': 4099, 'n_words': 927}