def get_grading_status_list(request): """ Get a list of locations where student has submitted open ended questions and the status of each. Input: Course id, student id Output: Dictionary containing success, and a list of problems in the course with student submission status. See grader_util for format details. """ if request.method != 'GET': return util._error_response("Request type must be GET", _INTERFACE_VERSION) for tag in ['course_id', 'student_id']: if tag not in request.GET: return util._error_response("Missing required key {0}".format(tag), _INTERFACE_VERSION) course_id = request.GET.get('course_id') student_id = request.GET.get('student_id') success, sub_list = grader_util.get_problems_student_has_tried(student_id, course_id) if not success: return util._error_response("Could not generate a submission list. {0}".format(sub_list),_INTERFACE_VERSION) problem_list_dict={ 'success' : success, 'problem_list' : sub_list, } util.log_connection_data() return util._success_response(problem_list_dict, _INTERFACE_VERSION)
def get_pending_count(request): """ Returns the number of submissions pending grading """ if cache.get(NOTHING_TO_ML_GRADE_CACHE_KEY): # If get_submission_ml resulted in no ml grading being found, then return pending count as 0. # When cache timeout expires, it will check again. This saves us from excessive calls to # get_submission_ml. to_be_graded_count = 0 else: if request.method != 'GET': return util._error_response("'get_pending_count' must use HTTP GET", _INTERFACE_VERSION) grader_type = request.GET.get("grader_type") if not grader_type: return util._error_response("grader type is a needed key", _INTERFACE_VERSION) if grader_type not in [i[0] for i in GRADER_TYPE]: return util._error_response("invalid grader type", _INTERFACE_VERSION) to_be_graded_count = Submission.objects.filter( state=SubmissionState.waiting_to_be_graded, next_grader_type=grader_type, ).count() util.log_connection_data() return util._success_response({'to_be_graded_count' : to_be_graded_count}, _INTERFACE_VERSION)
def get_submission_ml(request): """ Gets a submission for the ML grader Input: Get request with no parameters """ unique_locations = [ x['location'] for x in list(Submission.objects.values('location').distinct()) ] for location in unique_locations: sl = staff_grading_util.StaffLocation(location) subs_graded_by_instructor = sl.graded_count() success = ml_grading_util.check_for_all_model_and_rubric_success( location) if subs_graded_by_instructor >= settings.MIN_TO_USE_ML and success: to_be_graded = Submission.objects.filter( location=location, state=SubmissionState.waiting_to_be_graded, next_grader_type="ML", ) if (to_be_graded.count() > 0): to_be_graded = to_be_graded[0] if to_be_graded is not None: to_be_graded.state = SubmissionState.being_graded to_be_graded.save() return util._success_response( {'submission_id': to_be_graded.id}, _INTERFACE_VERSION) util.log_connection_data() return util._error_response("Nothing to grade.", _INTERFACE_VERSION)
def take_action_on_flags(request): if request.method != 'POST': return util._error_response("Request type must be POST", _INTERFACE_VERSION) for tag in ['course_id', 'student_id', 'submission_id', 'action_type']: if tag not in request.POST: return util._error_response("Missing required key {0}".format(tag), _INTERFACE_VERSION) course_id = request.POST.get('course_id') student_id = request.POST.get('student_id') submission_id = request.POST.get('submission_id') action_type = request.POST.get('action_type') success, data = peer_grading_util.take_action_on_flags(course_id, student_id, submission_id, action_type) log.debug(data) if not success: return util._error_response(data,_INTERFACE_VERSION) submission_dict={ 'success' : success, 'data' : data, } util.log_connection_data() return util._success_response(submission_dict, _INTERFACE_VERSION)
def check_for_notifications(request): """ Check if a given problem name, location tuple is unique Input: A problem location and the problem name Output: Dictionary containing success, and and indicator of whether or not the name is unique """ if request.method != 'GET': return util._error_response("Request type must be GET", _INTERFACE_VERSION) for tag in [ 'course_id', 'user_is_staff', 'last_time_viewed', 'student_id' ]: if tag not in request.GET: return util._error_response("Missing required key {0}".format(tag), _INTERFACE_VERSION) request_dict = request.GET.copy() success, combined_notifications = grader_util.check_for_combined_notifications( request_dict) if not success: return util._error_response(combined_notifications, _INTERFACE_VERSION) util.log_connection_data() return util._success_response(combined_notifications, _INTERFACE_VERSION)
def get_pending_count(request): """ Returns the number of submissions pending grading """ if cache.get(NOTHING_TO_ML_GRADE_CACHE_KEY): # If get_submission_ml resulted in no ml grading being found, then return pending count as 0. # When cache timeout expires, it will check again. This saves us from excessive calls to # get_submission_ml. to_be_graded_count = 0 else: if request.method != 'GET': return util._error_response( "'get_pending_count' must use HTTP GET", _INTERFACE_VERSION) grader_type = request.GET.get("grader_type") if not grader_type: return util._error_response("grader type is a needed key", _INTERFACE_VERSION) if grader_type not in [i[0] for i in GRADER_TYPE]: return util._error_response("invalid grader type", _INTERFACE_VERSION) to_be_graded_count = Submission.objects.filter( state=SubmissionState.waiting_to_be_graded, next_grader_type=grader_type, ).count() util.log_connection_data() return util._success_response({'to_be_graded_count': to_be_graded_count}, _INTERFACE_VERSION)
def get_submission_ml(request): """ Gets a submission for the ML grader Input: Get request with no parameters """ unique_locations = [x["location"] for x in list(Submission.objects.values("location").distinct())] for location in unique_locations: subs_graded_by_instructor = staff_grading_util.finished_submissions_graded_by_instructor(location).count() success = ml_grading_util.check_for_all_model_and_rubric_success(location) if subs_graded_by_instructor >= settings.MIN_TO_USE_ML and success: to_be_graded = Submission.objects.filter( location=location, state=SubmissionState.waiting_to_be_graded, next_grader_type="ML" ) if to_be_graded.count() > 0: to_be_graded = to_be_graded[0] if to_be_graded is not None: to_be_graded.state = SubmissionState.being_graded to_be_graded.save() # Insert timing initialization code initialize_timing(to_be_graded) return util._success_response({"submission_id": to_be_graded.id}, _INTERFACE_VERSION) util.log_connection_data() return util._error_response("Nothing to grade.", _INTERFACE_VERSION)
def get_pending_count(request): """ Returns the number of submissions pending grading """ if request.method != 'GET': return util._error_response("'get_pending_count' must use HTTP GET", _INTERFACE_VERSION) grader_type = request.GET.get("grader_type") if not grader_type: return util._error_response("grader type is a needed key", _INTERFACE_VERSION) if grader_type not in [i[0] for i in GRADER_TYPE]: return util._error_response("invalid grader type", _INTERFACE_VERSION) to_be_graded_count = Submission.objects.filter( state=SubmissionState.waiting_to_be_graded, next_grader_type=grader_type, ).count() util.log_connection_data() return util._success_response({'to_be_graded_count': to_be_graded_count}, _INTERFACE_VERSION)
def take_action_on_flags(request): if request.method != 'POST': return util._error_response("Request type must be POST", _INTERFACE_VERSION) for tag in ['course_id', 'student_id', 'submission_id', 'action_type']: if tag not in request.POST: return util._error_response("Missing required key {0}".format(tag), _INTERFACE_VERSION) course_id = request.POST.get('course_id') student_id = request.POST.get('student_id') submission_id = request.POST.get('submission_id') action_type = request.POST.get('action_type') success, data = peer_grading_util.take_action_on_flags(course_id, student_id, submission_id, action_type) if not success: return util._error_response(data,_INTERFACE_VERSION) submission_dict={ 'success' : success, 'data' : data, } util.log_connection_data() return util._success_response(submission_dict, _INTERFACE_VERSION)
def put_result(request): """ Used by external interfaces to post results back to controller """ if request.method != 'POST': return util._error_response("'put_result' must use HTTP POST", _INTERFACE_VERSION) else: post_data = request.POST.dict().copy() for tag in ['feedback', 'submission_id', 'grader_type', 'status', 'confidence', 'grader_id', 'score', 'errors', 'rubric_scores_complete', 'rubric_scores']: if not post_data.has_key(tag): return util._error_response("Failed to find needed key {0}.".format(tag), _INTERFACE_VERSION) #list comprehension below just gets all available grader types ['ML','IN', etc if post_data['grader_type'] not in [i[0] for i in GRADER_TYPE]: return util._error_response("Invalid grader type {0}.".format(post_data['grader_type']),_INTERFACE_VERSION) #list comprehension below gets all available status codes ['F',"S'] if post_data['status'] not in [i[0] for i in STATUS_CODES]: return util._error_response("Invalid grader status.".format(post_data['status']), _INTERFACE_VERSION) try: post_data['score'] = int(post_data['score']) except Exception: return util._error_response("Can't parse score {0} into an int.".format(post_data['score']), _INTERFACE_VERSION) try: sub=Submission.objects.get(id=int(post_data['submission_id'])) except Exception: return util._error_response( "Submission id {0} is not valid.".format(post_data.get('submission_id', "NA")), _INTERFACE_VERSION, ) rubric_scores_complete = request.POST.get('rubric_scores_complete', False) rubric_scores = request.POST.get('rubric_scores', []) try: rubric_scores=json.loads(rubric_scores) except Exception: pass success, error_message = grader_util.validate_rubric_scores(rubric_scores, rubric_scores_complete, sub) if not success: return util._error_response( error_message, _INTERFACE_VERSION, ) post_data['rubric_scores']=rubric_scores post_data['rubric_scores_complete'] = rubric_scores_complete success, header = grader_util.create_and_handle_grader_object(post_data) if not success: return util._error_response("Could not save grader.", _INTERFACE_VERSION) util.log_connection_data() return util._success_response({'message' : "Saved successfully."}, _INTERFACE_VERSION)
def put_result(request): """ Used by external interfaces to post results back to controller """ if request.method != 'POST': return util._error_response("'put_result' must use HTTP POST", _INTERFACE_VERSION) else: post_data = request.POST.dict().copy() for tag in ['feedback', 'submission_id', 'grader_type', 'status', 'confidence', 'grader_id', 'score', 'errors', 'rubric_scores_complete', 'rubric_scores']: if not post_data.has_key(tag): return util._error_response("Failed to find needed key {0}.".format(tag), _INTERFACE_VERSION) #list comprehension below just gets all available grader types ['ML','IN', etc if post_data['grader_type'] not in [i[0] for i in GRADER_TYPE]: return util._error_response("Invalid grader type {0}.".format(post_data['grader_type']),_INTERFACE_VERSION) #list comprehension below gets all available status codes ['F',"S'] if post_data['status'] not in [i[0] for i in STATUS_CODES]: return util._error_response("Invalid grader status.".format(post_data['status']), _INTERFACE_VERSION) try: post_data['score'] = int(post_data['score']) except: return util._error_response("Can't parse score {0} into an int.".format(post_data['score']), _INTERFACE_VERSION) try: sub=Submission.objects.get(id=int(post_data['submission_id'])) except: return util._error_response( "Submission id {0} is not valid.".format(post_data.get('submission_id', "NA")), _INTERFACE_VERSION, ) rubric_scores_complete = request.POST.get('rubric_scores_complete', False) rubric_scores = request.POST.get('rubric_scores', []) try: rubric_scores=json.loads(rubric_scores) except: pass success, error_message = grader_util.validate_rubric_scores(rubric_scores, rubric_scores_complete, sub) if not success: return util._error_response( error_message, _INTERFACE_VERSION, ) post_data['rubric_scores']=rubric_scores post_data['rubric_scores_complete'] = rubric_scores_complete success, header = grader_util.create_and_handle_grader_object(post_data) if not success: return util._error_response("Could not save grader.", _INTERFACE_VERSION) util.log_connection_data() return util._success_response({'message' : "Saved successfully."}, _INTERFACE_VERSION)
def get_submission_ml(request): """ Gets a submission for the ML grader Input: Get request with no parameters """ unique_locations = [ x['location'] for x in list(Submission.objects.values('location').distinct()) ] for location in unique_locations: nothing_to_ml_grade_for_location_key = NOTHING_TO_ML_GRADE_LOCATION_CACHE_KEY.format( location=location) # Go to the next location if we have recently determined that a location # has no ML grading ready. if cache.get(nothing_to_ml_grade_for_location_key): continue sl = staff_grading_util.StaffLocation(location) control = SubmissionControl(sl.latest_submission()) subs_graded_by_instructor = sl.graded_count() success = ml_grading_util.check_for_all_model_and_rubric_success( location) if subs_graded_by_instructor >= control.minimum_to_use_ai and success: to_be_graded = Submission.objects.filter( location=location, state=SubmissionState.waiting_to_be_graded, next_grader_type="ML", ) if (to_be_graded.count() > 0): to_be_graded = to_be_graded[0] if to_be_graded is not None: to_be_graded.state = SubmissionState.being_graded to_be_graded.save() return util._success_response( {'submission_id': to_be_graded.id}, _INTERFACE_VERSION) # If we don't get a submission to return, then there is no ML grading for this location. # Cache this boolean to avoid an expensive loop iteration. cache.set(nothing_to_ml_grade_for_location_key, True, settings.RECHECK_EMPTY_ML_GRADE_QUEUE_DELAY) util.log_connection_data() # Set this cache key to ensure that this expensive function isn't repeatedly called when not needed. cache.set(NOTHING_TO_ML_GRADE_CACHE_KEY, True, settings.RECHECK_EMPTY_ML_GRADE_QUEUE_DELAY) return util._error_response("Nothing to grade.", _INTERFACE_VERSION)
def get_submission_instructor(request): """ Gets a submission for the Instructor grading view """ try: course_id = util._value_or_default(request.GET['course_id'], None) except Exception: return util._error_response("'get_submission' requires parameter 'course_id'", _INTERFACE_VERSION) sc = staff_grading_util.StaffCourse(course_id) found, sub_id = sc.next_item() if not found: return util._error_response("Nothing to grade.", _INTERFACE_VERSION) util.log_connection_data() return util._success_response({'submission_id' : sub_id}, _INTERFACE_VERSION)
def get_submission_peer(request): """ Gets a submission for the Peer grading view """ try: location = util._value_or_default(request.GET['location'], None) grader_id = util._value_or_default(request.GET['grader_id'], None) except KeyError: return util._error_response("'get_submission' requires parameters 'location', 'grader_id'", _INTERFACE_VERSION) pl = peer_grading_util.PeerLocation(location, grader_id) found, sub_id = pl.next_item() if not found: return util._error_response("Nothing to grade.", _INTERFACE_VERSION) util.log_connection_data() return util._success_response({'submission_id' : sub_id}, _INTERFACE_VERSION)
def get_submission_instructor(request): """ Gets a submission for the Instructor grading view """ try: course_id = util._value_or_default(request.GET['course_id'], None) except: return util._error_response("'get_submission' requires parameter 'course_id'", _INTERFACE_VERSION) found, sub_id = staff_grading_util.get_single_instructor_grading_item(course_id) if not found: return util._error_response("Nothing to grade.", _INTERFACE_VERSION) #Insert timing initialization code initialize_timing(sub_id) util.log_connection_data() return util._success_response({'submission_id' : sub_id}, _INTERFACE_VERSION)
def get_submission_instructor(request): """ Gets a submission for the Instructor grading view """ try: course_id = util._value_or_default(request.GET["course_id"], None) except: return util._error_response("'get_submission' requires parameter 'course_id'", _INTERFACE_VERSION) found, sub_id = staff_grading_util.get_single_instructor_grading_item(course_id) if not found: return util._error_response("Nothing to grade.", _INTERFACE_VERSION) # Insert timing initialization code initialize_timing(sub_id) util.log_connection_data() return util._success_response({"submission_id": sub_id}, _INTERFACE_VERSION)
def get_submission_instructor(request): """ Gets a submission for the Instructor grading view """ try: course_id = util._value_or_default(request.GET['course_id'], None) except Exception: return util._error_response( "'get_submission' requires parameter 'course_id'", _INTERFACE_VERSION) sc = staff_grading_util.StaffCourse(course_id) found, sub_id = sc.next_item() if not found: return util._error_response("Nothing to grade.", _INTERFACE_VERSION) util.log_connection_data() return util._success_response({'submission_id': sub_id}, _INTERFACE_VERSION)
def get_submission_peer(request): """ Gets a submission for the Peer grading view """ try: location = util._value_or_default(request.GET["location"], None) grader_id = util._value_or_default(request.GET["grader_id"], None) except KeyError: return util._error_response("'get_submission' requires parameters 'location', 'grader_id'", _INTERFACE_VERSION) found, sub_id = peer_grading_util.get_single_peer_grading_item(location, grader_id) if not found: return util._error_response("Nothing to grade.", _INTERFACE_VERSION) # Insert timing initialization code initialize_timing(sub_id) util.log_connection_data() return util._success_response({"submission_id": sub_id}, _INTERFACE_VERSION)
def get_flagged_problem_list(request): if request.method != 'GET': return util._error_response("Request type must be GET", _INTERFACE_VERSION) for tag in ['course_id']: if tag not in request.GET: return util._error_response("Missing required key {0}".format(tag), _INTERFACE_VERSION) success, flagged_submissions = peer_grading_util.get_flagged_submissions(request.GET.get('course_id')) if not success: return util._error_response(flagged_submissions,_INTERFACE_VERSION) flagged_submission_dict={ 'success' : success, 'flagged_submissions' : flagged_submissions, } util.log_connection_data() return util._success_response(flagged_submission_dict, _INTERFACE_VERSION)
def get_submission_peer(request): """ Gets a submission for the Peer grading view """ try: location = util._value_or_default(request.GET['location'], None) grader_id = util._value_or_default(request.GET['grader_id'], None) except KeyError: return util._error_response("'get_submission' requires parameters 'location', 'grader_id'", _INTERFACE_VERSION) found, sub_id = peer_grading_util.get_single_peer_grading_item(location, grader_id) if not found: return util._error_response("Nothing to grade.", _INTERFACE_VERSION) #Insert timing initialization code initialize_timing(sub_id) util.log_connection_data() return util._success_response({'submission_id' : sub_id}, _INTERFACE_VERSION)
def get_pending_count(request): """ Returns the number of submissions pending grading """ if request.method != "GET": return util._error_response("'get_pending_count' must use HTTP GET", _INTERFACE_VERSION) grader_type = request.GET.get("grader_type") if not grader_type: return util._error_response("grader type is a needed key", _INTERFACE_VERSION) if grader_type not in [i[0] for i in GRADER_TYPE]: return util._error_response("invalid grader type", _INTERFACE_VERSION) to_be_graded_count = Submission.objects.filter( state=SubmissionState.waiting_to_be_graded, next_grader_type=grader_type ).count() util.log_connection_data() return util._success_response({"to_be_graded_count": to_be_graded_count}, _INTERFACE_VERSION)
def get_submission_ml(request): """ Gets a submission for the ML grader Input: Get request with no parameters """ unique_locations = [x['location'] for x in list(Submission.objects.values('location').distinct())] for location in unique_locations: nothing_to_ml_grade_for_location_key = NOTHING_TO_ML_GRADE_LOCATION_CACHE_KEY.format(location=location) # Go to the next location if we have recently determined that a location # has no ML grading ready. if cache.get(nothing_to_ml_grade_for_location_key): continue sl = staff_grading_util.StaffLocation(location) subs_graded_by_instructor = sl.graded_count() success = ml_grading_util.check_for_all_model_and_rubric_success(location) if subs_graded_by_instructor >= settings.MIN_TO_USE_ML and success: to_be_graded = Submission.objects.filter( location=location, state=SubmissionState.waiting_to_be_graded, next_grader_type="ML", ) if(to_be_graded.count() > 0): to_be_graded = to_be_graded[0] if to_be_graded is not None: to_be_graded.state = SubmissionState.being_graded to_be_graded.save() return util._success_response({'submission_id' : to_be_graded.id}, _INTERFACE_VERSION) # If we don't get a submission to return, then there is no ML grading for this location. # Cache this boolean to avoid an expensive loop iteration. cache.set(nothing_to_ml_grade_for_location_key, True, settings.RECHECK_EMPTY_ML_GRADE_QUEUE_DELAY) util.log_connection_data() # Set this cache key to ensure that this expensive function isn't repeatedly called when not needed. cache.set(NOTHING_TO_ML_GRADE_CACHE_KEY, True, settings.RECHECK_EMPTY_ML_GRADE_QUEUE_DELAY) return util._error_response("Nothing to grade.", _INTERFACE_VERSION)
def check_for_notifications(request): """ Check if a given problem name, location tuple is unique Input: A problem location and the problem name Output: Dictionary containing success, and and indicator of whether or not the name is unique """ if request.method != 'GET': return util._error_response("Request type must be GET", _INTERFACE_VERSION) for tag in ['course_id', 'user_is_staff', 'last_time_viewed', 'student_id']: if tag not in request.GET: return util._error_response("Missing required key {0}".format(tag), _INTERFACE_VERSION) request_dict = request.GET.copy() success, combined_notifications = grader_util.check_for_combined_notifications(request_dict) if not success: return util._error_response(combined_notifications,_INTERFACE_VERSION) util.log_connection_data() return util._success_response(combined_notifications, _INTERFACE_VERSION)
def submit(request): ''' Xqueue pull script posts objects here. Input: request - dict with keys xqueue_header and xqueue_body xqueue_header needs submission_id,submission_key,queue_name xqueue_body needs grader_payload, student_info, student_response, max_score grader_payload needs location, course_id,problem_id,grader student_info needs anonymous_student_id, submission_time Output: Returns status code indicating success (0) or failure (1) and message ''' transaction.commit_unless_managed() if request.method != 'POST': return util._error_response("'submit' must use HTTP POST", _INTERFACE_VERSION) else: #Minimal parsing of reply reply_is_valid, header, body = _is_valid_reply(request.POST.copy()) if not reply_is_valid: log.error("Invalid xqueue object added: request_ip: {0} request.POST: {1}".format( util.get_request_ip(request), request.POST, )) statsd.increment("open_ended_assessment.grading_controller.controller.xqueue_interface.submit", tags=["success:Exception"]) return util._error_response('Incorrect format', _INTERFACE_VERSION) else: try: #Retrieve individual values from xqueue body and header. prompt = util._value_or_default(body['grader_payload']['prompt'], "") rubric = util._value_or_default(body['grader_payload']['rubric'], "") student_id = util._value_or_default(body['student_info']['anonymous_student_id']) location = util._value_or_default(body['grader_payload']['location']) course_id = util._value_or_default(body['grader_payload']['course_id']) problem_id = util._value_or_default(body['grader_payload']['problem_id'], location) grader_settings = util._value_or_default(body['grader_payload']['grader_settings'], "") student_response = util._value_or_default(body['student_response']) student_response = util.sanitize_html(student_response) xqueue_submission_id = util._value_or_default(header['submission_id']) xqueue_submission_key = util._value_or_default(header['submission_key']) state_code = SubmissionState.waiting_to_be_graded xqueue_queue_name = util._value_or_default(header["queue_name"]) max_score = util._value_or_default(body['max_score']) submission_time_string = util._value_or_default(body['student_info']['submission_time']) student_submission_time = datetime.strptime(submission_time_string, "%Y%m%d%H%M%S") skip_basic_checks = util._value_or_default(body['grader_payload']['skip_basic_checks'], False) if isinstance(skip_basic_checks, basestring): skip_basic_checks = (skip_basic_checks.lower() == "true") #TODO: find a better way to do this #Need to set rubric to whatever the first submission for this location had #as its rubric. If the rubric is changed in the course XML, it will break things. try: first_sub_for_location=Submission.objects.filter(location=location).order_by('date_created')[0] rubric= first_sub_for_location.rubric except: error_message="Could not find an existing submission in location. rubric is original." log.info(error_message) initial_display="" if 'initial_display' in body['grader_payload'].keys(): initial_display = util._value_or_default(body['grader_payload']['initial_display'], "") answer="" if 'answer' in body['grader_payload'].keys(): answer = util._value_or_default(body['grader_payload']['answer'], "") #Sleep for some time to allow other pull_from_xqueue processes to get behind/ahead time_sleep_value = random.uniform(0, .1) time.sleep(time_sleep_value) transaction.commit_unless_managed() #Without this, sometimes a race condition creates duplicate submissions sub_count = Submission.objects.filter( prompt=prompt, rubric=rubric, student_id=student_id, problem_id=problem_id, student_submission_time=student_submission_time, xqueue_submission_id=xqueue_submission_id, xqueue_submission_key=xqueue_submission_key, xqueue_queue_name=xqueue_queue_name, location=location, course_id=course_id, grader_settings=grader_settings, ).count() if sub_count>0: log.error("Exact submission already exists.") return util._error_response('Submission already exists.', _INTERFACE_VERSION) transaction.commit_unless_managed() #Create submission object sub, created = Submission.objects.get_or_create( prompt=prompt, rubric=rubric, student_id=student_id, problem_id=problem_id, state=state_code, student_response=student_response, student_submission_time=student_submission_time, xqueue_submission_id=xqueue_submission_id, xqueue_submission_key=xqueue_submission_key, xqueue_queue_name=xqueue_queue_name, location=location, course_id=course_id, max_score=max_score, grader_settings=grader_settings, initial_display=initial_display, answer=answer, skip_basic_checks = skip_basic_checks, ) transaction.commit_unless_managed() if created==False: log.error("Exact submission already exists.") return util._error_response('Submission already exists.', _INTERFACE_VERSION) except Exception as err: xqueue_submission_id = util._value_or_default(header['submission_id']) xqueue_submission_key = util._value_or_default(header['submission_key']) log.exception( "Error creating submission and adding to database: sender: {0}, submission_id: {1}, submission_key: {2}".format( util.get_request_ip(request), xqueue_submission_id, xqueue_submission_key, )) statsd.increment("open_ended_assessment.grading_controller.controller.xqueue_interface.submit", tags=["success:Exception"]) return util._error_response('Unable to create submission.', _INTERFACE_VERSION) #Handle submission and write to db success = handle_submission(sub) statsd.increment("open_ended_assessment.grading_controller.controller.xqueue_interface.submit", tags=[ "success:{0}".format(success), "location:{0}".format(sub.location), "course_id:{0}".format(course_id), ]) transaction.commit_unless_managed() if not success: return util._error_response("Failed to handle submission.", _INTERFACE_VERSION) util.log_connection_data() transaction.commit_unless_managed() return util._success_response({'message': "Saved successfully."}, _INTERFACE_VERSION)
def submit(request): ''' Xqueue pull script posts objects here. Input: request - dict with keys xqueue_header and xqueue_body xqueue_header needs submission_id,submission_key,queue_name xqueue_body needs grader_payload, student_info, student_response, max_score grader_payload needs location, course_id,problem_id,grader student_info needs anonymous_student_id, submission_time Output: Returns status code indicating success (0) or failure (1) and message ''' transaction.commit_unless_managed() if request.method != 'POST': return util._error_response("'submit' must use HTTP POST", _INTERFACE_VERSION) else: #Minimal parsing of reply reply_is_valid, header, body = _is_valid_reply(request.POST.copy()) if not reply_is_valid: log.error( "Invalid xqueue object added: request_ip: {0} request.POST: {1}" .format( util.get_request_ip(request), request.POST, )) statsd.increment( "open_ended_assessment.grading_controller.controller.xqueue_interface.submit", tags=["success:Exception"]) return util._error_response('Incorrect format', _INTERFACE_VERSION) else: try: #Retrieve individual values from xqueue body and header. prompt = util._value_or_default( body['grader_payload']['prompt'], "") rubric = util._value_or_default( body['grader_payload']['rubric'], "") student_id = util._value_or_default( body['student_info']['anonymous_student_id']) location = util._value_or_default( body['grader_payload']['location']) course_id = util._value_or_default( body['grader_payload']['course_id']) problem_id = util._value_or_default( body['grader_payload']['problem_id'], location) grader_settings = util._value_or_default( body['grader_payload']['grader_settings'], "") student_response = util._value_or_default( body['student_response']) student_response = util.sanitize_html(student_response) xqueue_submission_id = util._value_or_default( header['submission_id']) xqueue_submission_key = util._value_or_default( header['submission_key']) state_code = SubmissionState.waiting_to_be_graded xqueue_queue_name = util._value_or_default( header["queue_name"]) max_score = util._value_or_default(body['max_score']) submission_time_string = util._value_or_default( body['student_info']['submission_time']) student_submission_time = datetime.strptime( submission_time_string, "%Y%m%d%H%M%S") control_fields = body['grader_payload'].get('control', {}) try: control_fields = json.loads(control_fields) except Exception: pass skip_basic_checks = util._value_or_default( body['grader_payload']['skip_basic_checks'], False) if isinstance(skip_basic_checks, basestring): skip_basic_checks = (skip_basic_checks.lower() == "true") #TODO: find a better way to do this #Need to set rubric to whatever the first submission for this location had #as its rubric. If the rubric is changed in the course XML, it will break things. try: first_sub_for_location = Submission.objects.filter( location=location).order_by('date_created')[0] rubric = first_sub_for_location.rubric except Exception: error_message = "Could not find an existing submission in location. rubric is original." log.info(error_message) initial_display = "" if 'initial_display' in body['grader_payload'].keys(): initial_display = util._value_or_default( body['grader_payload']['initial_display'], "") answer = "" if 'answer' in body['grader_payload'].keys(): answer = util._value_or_default( body['grader_payload']['answer'], "") #Sleep for some time to allow other pull_from_xqueue processes to get behind/ahead time_sleep_value = random.uniform(0, .1) time.sleep(time_sleep_value) transaction.commit_unless_managed() #Without this, sometimes a race condition creates duplicate submissions sub_count = Submission.objects.filter( prompt=prompt, rubric=rubric, student_id=student_id, problem_id=problem_id, student_submission_time=student_submission_time, xqueue_submission_id=xqueue_submission_id, xqueue_submission_key=xqueue_submission_key, xqueue_queue_name=xqueue_queue_name, location=location, course_id=course_id, grader_settings=grader_settings, ).count() if sub_count > 0: return util._error_response('Submission already exists.', _INTERFACE_VERSION) transaction.commit_unless_managed() #Create submission object sub, created = Submission.objects.get_or_create( prompt=prompt, rubric=rubric, student_id=student_id, problem_id=problem_id, state=state_code, student_response=student_response, student_submission_time=student_submission_time, xqueue_submission_id=xqueue_submission_id, xqueue_submission_key=xqueue_submission_key, xqueue_queue_name=xqueue_queue_name, location=location, course_id=course_id, max_score=max_score, grader_settings=grader_settings, initial_display=initial_display, answer=answer, skip_basic_checks=skip_basic_checks, control_fields=json.dumps(control_fields)) transaction.commit_unless_managed() if created == False: return util._error_response('Submission already exists.', _INTERFACE_VERSION) except Exception as err: xqueue_submission_id = util._value_or_default( header['submission_id']) xqueue_submission_key = util._value_or_default( header['submission_key']) log.exception( "Error creating submission and adding to database: sender: {0}, submission_id: {1}, submission_key: {2}" .format( util.get_request_ip(request), xqueue_submission_id, xqueue_submission_key, )) statsd.increment( "open_ended_assessment.grading_controller.controller.xqueue_interface.submit", tags=["success:Exception"]) return util._error_response('Unable to create submission.', _INTERFACE_VERSION) #Handle submission and write to db success = handle_submission(sub) statsd.increment( "open_ended_assessment.grading_controller.controller.xqueue_interface.submit", tags=[ "success:{0}".format(success), "location:{0}".format(sub.location), "course_id:{0}".format(course_id), ]) transaction.commit_unless_managed() if not success: return util._error_response("Failed to handle submission.", _INTERFACE_VERSION) util.log_connection_data() transaction.commit_unless_managed() return util._success_response({'message': "Saved successfully."}, _INTERFACE_VERSION)