api.py 4.57 KB
Newer Older
1 2 3
"""
API for the gating djangoapp
"""
4 5
import json
import logging
6 7 8
from collections import defaultdict

from opaque_keys.edx.keys import UsageKey
9

10
from lms.djangoapps.courseware.entrance_exams import get_entrance_exam_content
11
from openedx.core.lib.gating import api as gating_api
12
from util import milestones_helpers
13 14 15 16

log = logging.getLogger(__name__)


17 18
@gating_api.gating_enabled(default=False)
def evaluate_prerequisite(course, subsection_grade, user):
19
    """
20 21 22 23
    Evaluates any gating milestone relationships attached to the given
    subsection. If the subsection_grade meets the minimum score required
    by dependent subsections, the related milestone will be marked
    fulfilled for the user.
24
    """
25 26 27 28 29
    prereq_milestone = gating_api.get_gating_milestone(course.id, subsection_grade.location, 'fulfills')
    if prereq_milestone:
        gated_content_milestones = defaultdict(list)
        for milestone in gating_api.find_gating_milestones(course.id, content_key=None, relationship='requires'):
            gated_content_milestones[milestone['id']].append(milestone)
30

31 32 33 34 35 36 37 38 39
        gated_content = gated_content_milestones.get(prereq_milestone['id'])
        if gated_content:
            for milestone in gated_content:
                min_percentage = _get_minimum_required_percentage(milestone)
                subsection_percentage = _get_subsection_percentage(subsection_grade)
                if subsection_percentage >= min_percentage:
                    milestones_helpers.add_user_milestone({'id': user.id}, prereq_milestone)
                else:
                    milestones_helpers.remove_user_milestone({'id': user.id}, prereq_milestone)
40 41


42 43 44
def _get_minimum_required_percentage(milestone):
    """
    Returns the minimum percentage requirement for the given milestone.
45
    """
46 47 48 49 50 51 52 53
    # Default minimum score to 100
    min_score = 100
    requirements = milestone.get('requirements')
    if requirements:
        try:
            min_score = int(requirements.get('min_score'))
        except (ValueError, TypeError):
            log.warning(
54
                u'Gating: Failed to find minimum score for gating milestone %s, defaulting to 100',
55 56 57
                json.dumps(milestone)
            )
    return min_score
58 59


60 61 62 63
def _get_subsection_percentage(subsection_grade):
    """
    Returns the percentage value of the given subsection_grade.
    """
64
    return _calculate_ratio(subsection_grade.graded_total.earned, subsection_grade.graded_total.possible) * 100.0
65 66


67 68 69 70 71 72 73 74
def _calculate_ratio(earned, possible):
    """
    Returns the percentage of the given earned and possible values.
    """
    return float(earned) / float(possible) if possible else 0.0


def evaluate_entrance_exam(course_grade, user):
75
    """
76
    Evaluates any entrance exam milestone relationships attached
77 78
    to the given course. If the course_grade meets the
    minimum score required, the dependent milestones will be marked
79
    fulfilled for the user.
80
    """
81
    course = course_grade.course_data.course
82
    if milestones_helpers.is_entrance_exams_enabled() and getattr(course, 'entrance_exam_enabled', False):
83 84 85 86
        if get_entrance_exam_content(user, course):
            exam_chapter_key = get_entrance_exam_usage_key(course)
            exam_score_ratio = get_entrance_exam_score_ratio(course_grade, exam_chapter_key)
            if exam_score_ratio >= course.entrance_exam_minimum_score_pct:
87 88 89
                relationship_types = milestones_helpers.get_milestone_relationship_types()
                content_milestones = milestones_helpers.get_course_content_milestones(
                    course.id,
90
                    exam_chapter_key,
91 92
                    relationship=relationship_types['FULFILLS']
                )
93
                # Mark each entrance exam dependent milestone as fulfilled by the user.
94
                for milestone in content_milestones:
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
                    milestones_helpers.add_user_milestone({'id': user.id}, milestone)


def get_entrance_exam_usage_key(course):
    """
    Returns the UsageKey of the entrance exam for the course.
    """
    return UsageKey.from_string(course.entrance_exam_id).replace(course_key=course.id)


def get_entrance_exam_score_ratio(course_grade, exam_chapter_key):
    """
    Returns the score for the given chapter as a ratio of the
    aggregated earned over the possible points, resulting in a
    decimal value less than 1.
    """
    try:
        earned, possible = course_grade.score_for_chapter(exam_chapter_key)
    except KeyError:
        earned, possible = 0.0, 0.0
        log.warning(u'Gating: Unexpectedly failed to find chapter_grade for %s.', exam_chapter_key)
    return _calculate_ratio(earned, possible)