utils.py 6.92 KB
Newer Older
Calen Pennington committed
1
import logging
2

3
from xmodule.modulestore import search
4
from xmodule.modulestore.django import modulestore, ModuleI18nService
Vik Paruchuri committed
5
from xmodule.modulestore.exceptions import ItemNotFoundError, NoPathToItem
6 7 8 9 10 11
from xmodule.open_ended_grading_classes.controller_query_service import ControllerQueryService
from xmodule.open_ended_grading_classes.grading_service_module import GradingServiceError

from django.utils.translation import ugettext as _
from django.conf import settings

David Baumgold committed
12
from edxmako.shortcuts import render_to_string
13

14 15

log = logging.getLogger(__name__)
16

17 18 19 20 21 22
GRADER_DISPLAY_NAMES = {
    'ML': _("AI Assessment"),
    'PE': _("Peer Assessment"),
    'NA': _("Not yet available"),
    'BC': _("Automatic Checker"),
    'IN': _("Instructor Assessment"),
23
}
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38

STUDENT_ERROR_MESSAGE = _("Error occurred while contacting the grading service.  Please notify course staff.")
STAFF_ERROR_MESSAGE = _("Error occurred while contacting the grading service.  Please notify your edX point of contact.")


def generate_problem_url(problem_url_parts, base_course_url):
    """
    From a list of problem url parts generated by search.path_to_location and a base course url, generates a url to a problem
    @param problem_url_parts: Output of search.path_to_location
    @param base_course_url: Base url of a given course
    @return: A path to the problem
    """
    problem_url = base_course_url + "/"
    for i, part in enumerate(problem_url_parts):
        if part is not None:
39 40 41 42 43
            # This is the course_key. We need to turn it into its deprecated
            # form.
            if i == 0:
                part = part.to_deprecated_string()
            # This is placed between the course id and the rest of the url.
44 45 46 47 48
            if i == 1:
                problem_url += "courseware/"
            problem_url += part + "/"
    return problem_url

49

50
def does_location_exist(usage_key):
51 52 53 54 55 56
    """
    Checks to see if a valid module exists at a given location (ie has not been deleted)
    course_id - string course id
    location - string location
    """
    try:
57
        search.path_to_location(modulestore(), usage_key)
58 59
        return True
    except ItemNotFoundError:
60 61
        # If the problem cannot be found at the location received from the grading controller server,
        # it has been deleted by the course author.
Vik Paruchuri committed
62 63
        return False
    except NoPathToItem:
64
        # If the problem can be found, but there is no path to it, then we assume it is a draft.
65 66 67
        # Log a warning in any case.
        log.warn("Got an unexpected NoPathToItem error in staff grading with location %s. "
                 "This is ok if it is a draft; ensure that the location is valid.", usage_key)
68
        return False
69

70

71 72 73 74
def create_controller_query_service():
    """
    Return an instance of a service that can query edX ORA.
    """
75
    return ControllerQueryService(settings.OPEN_ENDED_GRADING_INTERFACE, render_to_string)
76

77

78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
class StudentProblemList(object):
    """
    Get a list of problems that the student has attempted from ORA.
    Add in metadata as needed.
    """
    def __init__(self, course_id, user_id):
        """
        @param course_id: The id of a course object.  Get using course.id.
        @param user_id: The anonymous id of the user, from the unique_id_for_user function.
        """
        self.course_id = course_id
        self.user_id = user_id

        # We want to append this string to all of our error messages.
        self.course_error_ending = _("for course {0} and student {1}.").format(self.course_id, user_id)

        # This is our generic error message.
        self.error_text = STUDENT_ERROR_MESSAGE
        self.success = False

        # Create a service to query edX ORA.
        self.controller_qs = create_controller_query_service()

    def fetch_from_grading_service(self):
        """
        Fetch a list of problems that the student has answered from ORA.
        Handle various error conditions.
        @return: A boolean success indicator.
        """
        # In the case of multiple calls, ensure that success is false initially.
        self.success = False
        try:
            #Get list of all open ended problems that the grading server knows about
111
            problem_list_dict = self.controller_qs.get_grading_status_list(self.course_id, self.user_id)
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
        except GradingServiceError:
            log.error("Problem contacting open ended grading service " + self.course_error_ending)
            return self.success
        except ValueError:
            log.error("Problem with results from external grading service for open ended" + self.course_error_ending)
            return self.success

        success = problem_list_dict['success']
        if 'error' in problem_list_dict:
            self.error_text = problem_list_dict['error']
            return success
        if 'problem_list' not in problem_list_dict:
            log.error("Did not receive a problem list in ORA response" + self.course_error_ending)
            return success

        self.problem_list = problem_list_dict['problem_list']

        self.success = True
        return self.success

    def add_problem_data(self, base_course_url):
        """
        Add metadata to problems.
        @param base_course_url: the base url for any course.  Can get with reverse('course')
        @return: A list of valid problems in the course and their appended data.
        """
        # Our list of valid problems.
        valid_problems = []

        if not self.success or not isinstance(self.problem_list, list):
            log.error("Called add_problem_data without a valid problem list" + self.course_error_ending)
            return valid_problems

        # Iterate through all of our problems and add data.
        for problem in self.problem_list:
            try:
                # Try to load the problem.
149 150
                usage_key = self.course_id.make_usage_key_from_deprecated_string(problem['location'])
                problem_url_parts = search.path_to_location(modulestore(), usage_key)
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
            except (ItemNotFoundError, NoPathToItem):
                # If the problem cannot be found at the location received from the grading controller server,
                # it has been deleted by the course author. We should not display it.
                error_message = "Could not find module for course {0} at location {1}".format(self.course_id,
                                                                                              problem['location'])
                log.error(error_message)
                continue

            # Get the problem url in the courseware.
            problem_url = generate_problem_url(problem_url_parts, base_course_url)

            # Map the grader name from ORA to a human readable version.
            grader_type_display_name = GRADER_DISPLAY_NAMES.get(problem['grader_type'], "edX Assessment")
            problem['actual_url'] = problem_url
            problem['grader_type_display_name'] = grader_type_display_name
            valid_problems.append(problem)
        return valid_problems