""" This module provides views that proxy to the staff grading backend service. """ import json import logging from django.conf import settings from django.http import HttpResponse, Http404 from django.utils.translation import ugettext as _ from opaque_keys.edx.locations import SlashSeparatedCourseKey from xmodule.open_ended_grading_classes.grading_service_module import GradingService, GradingServiceError from xmodule.modulestore.django import ModuleI18nService from courseware.access import has_access from lms.lib.xblock.runtime import LmsModuleSystem from edxmako.shortcuts import render_to_string from student.models import unique_id_for_user from open_ended_grading.utils import does_location_exist from dogapi import dog_stats_api log = logging.getLogger(__name__) STAFF_ERROR_MESSAGE = _( u'Could not contact the external grading server. Please contact the ' u'development team at {email}.' ).format( email=u' 0: return _err_response('Missing required keys {0}'.format( ', '.join(missing))) grader_id = unique_id_for_user(request.user) p = request.POST location = course_key.make_usage_key_from_deprecated_string(p['location']) return HttpResponse(json.dumps(_get_next(course_key, grader_id, location)), mimetype="application/json") def get_problem_list(request, course_id): """ Get all the problems for the given course id Returns a json dict with the following keys: success: bool problem_list: a list containing json dicts with the following keys: each dict represents a different problem in the course location: the location of the problem problem_name: the name of the problem num_graded: the number of responses that have been graded num_pending: the number of responses that are sitting in the queue min_for_ml: the number of responses that need to be graded before the ml can be run 'error': if success is False, will have an error message with more info. """ assert(isinstance(course_id, basestring)) course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) _check_access(request.user, course_key) try: response = staff_grading_service().get_problem_list(course_key, unique_id_for_user(request.user)) # If 'problem_list' is in the response, then we got a list of problems from the ORA server. # If it is not, then ORA could not find any problems. if 'problem_list' in response: problem_list = response['problem_list'] else: problem_list = [] # Make an error messages to reflect that we could not find anything to grade. response['error'] = _( u'Cannot find any open response problems in this course. ' u'Have you submitted answers to any open response assessment questions? ' u'If not, please do so and return to this page.' ) valid_problem_list = [] for i in xrange(0,len(problem_list)): # Needed to ensure that the 'location' key can be accessed. try: problem_list[i] = json.loads(problem_list[i]) except Exception: pass if does_location_exist(course_key.make_usage_key_from_deprecated_string(problem_list[i]['location'])): valid_problem_list.append(problem_list[i]) response['problem_list'] = valid_problem_list response = json.dumps(response) return HttpResponse(response, mimetype="application/json") except GradingServiceError: #This is a dev_facing_error log.exception( "Error from staff grading service in open " "ended grading. server url: {0}".format(staff_grading_service().url) ) #This is a staff_facing_error return HttpResponse(json.dumps({'success': False, 'error': STAFF_ERROR_MESSAGE})) def _get_next(course_id, grader_id, location): """ Implementation of get_next (also called from save_grade) -- returns a json string """ try: return staff_grading_service().get_next(course_id, location, grader_id) except GradingServiceError: #This is a dev facing error log.exception( "Error from staff grading service in open " "ended grading. server url: {0}".format(staff_grading_service().url) ) #This is a staff_facing_error return json.dumps({'success': False, 'error': STAFF_ERROR_MESSAGE}) def save_grade(request, course_id): """ Save the grade and feedback for a submission, and, if all goes well, return the next thing to grade. Expects the following POST parameters: 'score': int 'feedback': string 'submission_id': int Returns the same thing as get_next, except that additional error messages are possible if something goes wrong with saving the grade. """ course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) _check_access(request.user, course_key) if request.method != 'POST': raise Http404 p = request.POST required = set(['score', 'feedback', 'submission_id', 'location', 'submission_flagged']) skipped = 'skipped' in p #If the instructor has skipped grading the submission, then there will not be any rubric scores. #Only add in the rubric scores if the instructor has not skipped. if not skipped: required.add('rubric_scores[]') actual = set(p.keys()) missing = required - actual if len(missing) > 0: return _err_response('Missing required keys {0}'.format( ', '.join(missing))) success, message = check_feedback_length(p) if not success: return _err_response(message) grader_id = unique_id_for_user(request.user) location = course_key.make_usage_key_from_deprecated_string(p['location']) try: result = staff_grading_service().save_grade(course_key, grader_id, p['submission_id'], p['score'], p['feedback'], skipped, p.getlist('rubric_scores[]'), p['submission_flagged']) except GradingServiceError: #This is a dev_facing_error log.exception( "Error saving grade in the staff grading interface in open ended grading. Request: {0} Course ID: {1}".format( request, course_id)) #This is a staff_facing_error return _err_response(STAFF_ERROR_MESSAGE) except ValueError: #This is a dev_facing_error log.exception( "save_grade returned broken json in the staff grading interface in open ended grading: {0}".format( result_json)) #This is a staff_facing_error return _err_response(STAFF_ERROR_MESSAGE) if not result.get('success', False): #This is a dev_facing_error log.warning( 'Got success=False from staff grading service in open ended grading. Response: {0}'.format(result_json)) return _err_response(STAFF_ERROR_MESSAGE) # Ok, save_grade seemed to work. Get the next submission to grade. return HttpResponse(json.dumps(_get_next(course_id, grader_id, location)), mimetype="application/json") def check_feedback_length(data): feedback = data.get("feedback") if feedback and len(feedback) > MAX_ALLOWED_FEEDBACK_LENGTH: return False, "Feedback is too long, Max length is {0} characters.".format( MAX_ALLOWED_FEEDBACK_LENGTH ) else: return True, ""