diff --git a/cms/envs/common.py b/cms/envs/common.py index 2f203dc4d3..b91557505a 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -1016,7 +1016,6 @@ ADVANCED_COMPONENT_TYPES = [ 'rate', # Allows up-down voting of course content. See https://github.com/pmitros/RateXBlock 'split_test', - 'combinedopenended', 'peergrading', 'notes', 'schoolyourself_review', diff --git a/lms/djangoapps/open_ended_grading/__init__.py b/lms/djangoapps/open_ended_grading/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/lms/djangoapps/open_ended_grading/open_ended_notifications.py b/lms/djangoapps/open_ended_grading/open_ended_notifications.py deleted file mode 100644 index f4ef7335b1..0000000000 --- a/lms/djangoapps/open_ended_grading/open_ended_notifications.py +++ /dev/null @@ -1,199 +0,0 @@ -import datetime -import json -import logging - -from django.conf import settings - -from xmodule.open_ended_grading_classes import peer_grading_service -from xmodule.open_ended_grading_classes.controller_query_service import ControllerQueryService - -from courseware.access import has_access -from edxmako.shortcuts import render_to_string -from student.models import unique_id_for_user -from util.cache import cache - -from .staff_grading_service import StaffGradingService - -log = logging.getLogger(__name__) - -NOTIFICATION_CACHE_TIME = 300 -KEY_PREFIX = "open_ended_" - -NOTIFICATION_TYPES = ( - ('student_needs_to_peer_grade', 'peer_grading', 'Peer Grading'), - ('staff_needs_to_grade', 'staff_grading', 'Staff Grading'), - ('new_student_grading_to_view', 'open_ended_problems', 'Problems you have submitted'), - ('flagged_submissions_exist', 'open_ended_flagged_problems', 'Flagged Submissions') -) - - -def staff_grading_notifications(course, user): - staff_gs = StaffGradingService(settings.OPEN_ENDED_GRADING_INTERFACE) - pending_grading = False - img_path = "" - course_id = course.id - student_id = unique_id_for_user(user) - notification_type = "staff" - - success, notification_dict = get_value_from_cache(student_id, course_id, notification_type) - if success: - return notification_dict - - try: - notifications = json.loads(staff_gs.get_notifications(course_id)) - if notifications['success']: - if notifications['staff_needs_to_grade']: - pending_grading = True - except: - #Non catastrophic error, so no real action - notifications = {} - #This is a dev_facing_error - log.info( - "Problem with getting notifications from staff grading service for course {0} user {1}.".format(course_id, - student_id)) - - if pending_grading: - img_path = "/static/images/grading_notification.png" - - notification_dict = {'pending_grading': pending_grading, 'img_path': img_path, 'response': notifications} - - set_value_in_cache(student_id, course_id, notification_type, notification_dict) - - return notification_dict - - -def peer_grading_notifications(course, user): - peer_gs = peer_grading_service.PeerGradingService(settings.OPEN_ENDED_GRADING_INTERFACE, render_to_string) - pending_grading = False - img_path = "" - course_id = course.id - student_id = unique_id_for_user(user) - notification_type = "peer" - - success, notification_dict = get_value_from_cache(student_id, course_id, notification_type) - if success: - return notification_dict - - try: - notifications = json.loads(peer_gs.get_notifications(course_id, student_id)) - if notifications['success']: - if notifications['student_needs_to_peer_grade']: - pending_grading = True - except: - #Non catastrophic error, so no real action - notifications = {} - #This is a dev_facing_error - log.info( - "Problem with getting notifications from peer grading service for course {0} user {1}.".format(course_id, - student_id)) - if pending_grading: - img_path = "/static/images/grading_notification.png" - - notification_dict = {'pending_grading': pending_grading, 'img_path': img_path, 'response': notifications} - - set_value_in_cache(student_id, course_id, notification_type, notification_dict) - - return notification_dict - - -def combined_notifications(course, user): - """ - Show notifications to a given user for a given course. Get notifications from the cache if possible, - or from the grading controller server if not. - @param course: The course object for which we are getting notifications - @param user: The user object for which we are getting notifications - @return: A dictionary with boolean pending_grading (true if there is pending grading), img_path (for notification - image), and response (actual response from grading controller server). - """ - #Set up return values so that we can return them for error cases - pending_grading = False - img_path = "" - notifications = {} - notification_dict = {'pending_grading': pending_grading, 'img_path': img_path, 'response': notifications} - - #We don't want to show anonymous users anything. - if not user.is_authenticated(): - return notification_dict - - #Initialize controller query service using our mock system - controller_qs = ControllerQueryService(settings.OPEN_ENDED_GRADING_INTERFACE, render_to_string) - student_id = unique_id_for_user(user) - user_is_staff = bool(has_access(user, 'staff', course)) - course_id = course.id - notification_type = "combined" - - #See if we have a stored value in the cache - success, notification_dict = get_value_from_cache(student_id, course_id, notification_type) - if success: - return notification_dict - - #Get the time of the last login of the user - last_login = user.last_login - last_time_viewed = last_login - datetime.timedelta(seconds=(NOTIFICATION_CACHE_TIME + 60)) - - try: - #Get the notifications from the grading controller - notifications = controller_qs.check_combined_notifications( - course.id, - student_id, - user_is_staff, - last_time_viewed, - ) - if notifications.get('success'): - if (notifications.get('staff_needs_to_grade') or - notifications.get('student_needs_to_peer_grade')): - pending_grading = True - except: - #Non catastrophic error, so no real action - #This is a dev_facing_error - log.exception( - u"Problem with getting notifications from controller query service for course {0} user {1}.".format( - course_id, student_id)) - - if pending_grading: - img_path = "/static/images/grading_notification.png" - - notification_dict = {'pending_grading': pending_grading, 'img_path': img_path, 'response': notifications} - - #Store the notifications in the cache - set_value_in_cache(student_id, course_id, notification_type, notification_dict) - - return notification_dict - - -def get_value_from_cache(student_id, course_id, notification_type): - key_name = create_key_name(student_id, course_id, notification_type) - success, value = _get_value_from_cache(key_name) - return success, value - - -def set_value_in_cache(student_id, course_id, notification_type, value): - key_name = create_key_name(student_id, course_id, notification_type) - _set_value_in_cache(key_name, value) - - -def create_key_name(student_id, course_id, notification_type): - key_name = u"{prefix}{type}_{course}_{student}".format( - prefix=KEY_PREFIX, - type=notification_type, - course=course_id, - student=student_id, - ) - return key_name - - -def _get_value_from_cache(key_name): - value = cache.get(key_name) - success = False - if value is None: - return success, value - try: - value = json.loads(value) - success = True - except: - pass - return success, value - - -def _set_value_in_cache(key_name, value): - cache.set(key_name, json.dumps(value), NOTIFICATION_CACHE_TIME) diff --git a/lms/djangoapps/open_ended_grading/staff_grading.py b/lms/djangoapps/open_ended_grading/staff_grading.py deleted file mode 100644 index 3ea55f1df0..0000000000 --- a/lms/djangoapps/open_ended_grading/staff_grading.py +++ /dev/null @@ -1,24 +0,0 @@ -""" -LMS part of instructor grading: - -- views + ajax handling -- calls the instructor grading service -""" - -import logging - -log = logging.getLogger(__name__) - - -class StaffGrading(object): - """ - Wrap up functionality for staff grading of submissions--interface exposes get_html, ajax views. - """ - - def __init__(self, course): - self.course = course - - def get_html(self): - return "Instructor grading!" - # context = {} - # return render_to_string('courseware/instructor_grading_view.html', context) diff --git a/lms/djangoapps/open_ended_grading/staff_grading_service.py b/lms/djangoapps/open_ended_grading/staff_grading_service.py deleted file mode 100644 index 80cecbf9f3..0000000000 --- a/lms/djangoapps/open_ended_grading/staff_grading_service.py +++ /dev/null @@ -1,444 +0,0 @@ -""" -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 courseware.access import has_access -from edxmako.shortcuts import render_to_string -from student.models import unique_id_for_user - -from open_ended_grading.utils import does_location_exist -import dogstats_wrapper as 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)), - content_type="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(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, content_type="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)), - content_type="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, "" diff --git a/lms/djangoapps/open_ended_grading/tests.py b/lms/djangoapps/open_ended_grading/tests.py deleted file mode 100644 index 8ed7db9228..0000000000 --- a/lms/djangoapps/open_ended_grading/tests.py +++ /dev/null @@ -1,588 +0,0 @@ -""" -Tests for open ended grading interfaces - -./manage.py lms --settings test test lms/djangoapps/open_ended_grading -""" -import ddt -import json -import logging - -from django.conf import settings -from django.contrib.auth.models import User -from django.core.urlresolvers import reverse -from django.test import RequestFactory -from edxmako.shortcuts import render_to_string -from edxmako.tests import mako_middleware_process_request -from mock import MagicMock, patch, Mock -from opaque_keys.edx.locations import SlashSeparatedCourseKey -from xblock.field_data import DictFieldData -from xblock.fields import ScopeIds - -from config_models.models import cache -from courseware.tests import factories -from courseware.tests.helpers import LoginEnrollmentTestCase -from lms.djangoapps.lms_xblock.runtime import LmsModuleSystem -from student.roles import CourseStaffRole -from student.models import unique_id_for_user -from xblock_django.models import XBlockDisableConfig -from xmodule import peer_grading_module -from xmodule.error_module import ErrorDescriptor -from xmodule.modulestore.django import modulestore -from xmodule.modulestore.tests.django_utils import TEST_DATA_MIXED_TOY_MODULESTORE, ModuleStoreTestCase -from xmodule.modulestore.tests.factories import CourseFactory -from xmodule.modulestore.xml_importer import import_course_from_xml -from xmodule.open_ended_grading_classes import peer_grading_service, controller_query_service -from xmodule.tests import test_util_open_ended - -from open_ended_grading import staff_grading_service, views, utils - -TEST_DATA_DIR = settings.COMMON_TEST_DATA_ROOT - - -log = logging.getLogger(__name__) - - -class EmptyStaffGradingService(object): - """ - A staff grading service that does not return a problem list from get_problem_list. - Used for testing to see if error message for empty problem list is correctly displayed. - """ - - def get_problem_list(self, course_id, user_id): - """ - Return a staff grading response that is missing a problem list key. - """ - return {'success': True, 'error': 'No problems found.'} - - -def make_instructor(course, user_email): - """ - Makes a given user an instructor in a course. - """ - CourseStaffRole(course.id).add_users(User.objects.get(email=user_email)) - - -class StudentProblemListMockQuery(object): - """ - Mock controller query service for testing student problem list functionality. - """ - def get_grading_status_list(self, *args, **kwargs): - """ - Get a mock grading status list with locations from the open_ended test course. - @returns: grading status message dictionary. - """ - return { - "version": 1, - "problem_list": [ - { - "problem_name": "Test1", - "grader_type": "IN", - "eta_available": True, - "state": "Finished", - "eta": 259200, - "location": "i4x://edX/open_ended/combinedopenended/SampleQuestion1Attempt" - }, - { - "problem_name": "Test2", - "grader_type": "NA", - "eta_available": True, - "state": "Waiting to be Graded", - "eta": 259200, - "location": "i4x://edX/open_ended/combinedopenended/SampleQuestion" - }, - { - "problem_name": "Test3", - "grader_type": "PE", - "eta_available": True, - "state": "Waiting to be Graded", - "eta": 259200, - "location": "i4x://edX/open_ended/combinedopenended/SampleQuestion454" - }, - ], - "success": True - } - - -class TestStaffGradingService(ModuleStoreTestCase, LoginEnrollmentTestCase): - ''' - Check that staff grading service proxy works. Basically just checking the - access control and error handling logic -- all the actual work is on the - backend. - ''' - MODULESTORE = TEST_DATA_MIXED_TOY_MODULESTORE - - def setUp(self): - super(TestStaffGradingService, self).setUp() - self.student = 'view@test.com' - self.instructor = 'view2@test.com' - self.password = 'foo' - self.create_account('u1', self.student, self.password) - self.create_account('u2', self.instructor, self.password) - self.activate_user(self.student) - self.activate_user(self.instructor) - - self.course_id = SlashSeparatedCourseKey("edX", "toy", "2012_Fall") - self.location_string = self.course_id.make_usage_key('html', 'TestLocation').to_deprecated_string() - self.toy = modulestore().get_course(self.course_id) - - make_instructor(self.toy, self.instructor) - - self.mock_service = staff_grading_service.staff_grading_service() - - self.logout() - - def test_access(self): - """ - Make sure only staff have access. - """ - self.login(self.student, self.password) - - # both get and post should return 404 - for view_name in ('staff_grading_get_next', 'staff_grading_save_grade'): - url = reverse(view_name, kwargs={'course_id': self.course_id.to_deprecated_string()}) - self.assert_request_status_code(404, url, method="GET") - self.assert_request_status_code(404, url, method="POST") - - def test_get_next(self): - self.login(self.instructor, self.password) - - url = reverse('staff_grading_get_next', kwargs={'course_id': self.course_id.to_deprecated_string()}) - data = {'location': self.location_string} - - response = self.assert_request_status_code(200, url, method="POST", data=data) - - content = json.loads(response.content) - - self.assertTrue(content['success']) - self.assertEquals(content['submission_id'], self.mock_service.cnt) - self.assertIsNotNone(content['submission']) - self.assertIsNotNone(content['num_graded']) - self.assertIsNotNone(content['min_for_ml']) - self.assertIsNotNone(content['num_pending']) - self.assertIsNotNone(content['prompt']) - self.assertIsNotNone(content['ml_error_info']) - self.assertIsNotNone(content['max_score']) - self.assertIsNotNone(content['rubric']) - - def save_grade_base(self, skip=False): - self.login(self.instructor, self.password) - - url = reverse('staff_grading_save_grade', kwargs={'course_id': self.course_id.to_deprecated_string()}) - - data = {'score': '12', - 'feedback': 'great!', - 'submission_id': '123', - 'location': self.location_string, - 'submission_flagged': "true", - 'rubric_scores[]': ['1', '2']} - if skip: - data.update({'skipped': True}) - - response = self.assert_request_status_code(200, url, method="POST", data=data) - content = json.loads(response.content) - self.assertTrue(content['success'], str(content)) - self.assertEquals(content['submission_id'], self.mock_service.cnt) - - def test_save_grade(self): - self.save_grade_base(skip=False) - - def test_save_grade_skip(self): - self.save_grade_base(skip=True) - - def test_get_problem_list(self): - self.login(self.instructor, self.password) - - url = reverse('staff_grading_get_problem_list', kwargs={'course_id': self.course_id.to_deprecated_string()}) - data = {} - - response = self.assert_request_status_code(200, url, method="POST", data=data) - content = json.loads(response.content) - - self.assertTrue(content['success']) - self.assertEqual(content['problem_list'], []) - - @patch('open_ended_grading.staff_grading_service._service', EmptyStaffGradingService()) - def test_get_problem_list_missing(self): - """ - Test to see if a staff grading response missing a problem list is given the appropriate error. - Mock the staff grading service to enable the key to be missing. - """ - - # Get a valid user object. - instructor = User.objects.get(email=self.instructor) - # Mock a request object. - request = Mock( - user=instructor, - ) - # Get the response and load its content. - response = json.loads(staff_grading_service.get_problem_list(request, self.course_id.to_deprecated_string()).content) - - # A valid response will have an "error" key. - self.assertTrue('error' in response) - # Check that the error text is correct. - self.assertIn("Cannot find", response['error']) - - def test_save_grade_with_long_feedback(self): - """ - Test if feedback is too long save_grade() should return error message. - """ - self.login(self.instructor, self.password) - - url = reverse('staff_grading_save_grade', kwargs={'course_id': self.course_id.to_deprecated_string()}) - - data = { - 'score': '12', - 'feedback': '', - 'submission_id': '123', - 'location': self.location_string, - 'submission_flagged': "false", - 'rubric_scores[]': ['1', '2'] - } - - feedback_fragment = "This is very long feedback." - data["feedback"] = feedback_fragment * ( - (staff_grading_service.MAX_ALLOWED_FEEDBACK_LENGTH / len(feedback_fragment) + 1) - ) - - response = self.assert_request_status_code(200, url, method="POST", data=data) - content = json.loads(response.content) - - # Should not succeed. - self.assertEquals(content['success'], False) - self.assertEquals( - content['error'], - "Feedback is too long, Max length is {0} characters.".format( - staff_grading_service.MAX_ALLOWED_FEEDBACK_LENGTH - ) - ) - - -class TestPeerGradingService(ModuleStoreTestCase, LoginEnrollmentTestCase): - ''' - Check that staff grading service proxy works. Basically just checking the - access control and error handling logic -- all the actual work is on the - backend. - ''' - - def setUp(self): - super(TestPeerGradingService, self).setUp() - self.student = 'view@test.com' - self.instructor = 'view2@test.com' - self.password = 'foo' - self.create_account('u1', self.student, self.password) - self.create_account('u2', self.instructor, self.password) - self.activate_user(self.student) - self.activate_user(self.instructor) - - self.course_id = SlashSeparatedCourseKey("edX", "toy", "2012_Fall") - self.location_string = self.course_id.make_usage_key('html', 'TestLocation').to_deprecated_string() - self.toy = modulestore().get_course(self.course_id) - location = "i4x://edX/toy/peergrading/init" - field_data = DictFieldData({'data': "", 'location': location, 'category': 'peergrading'}) - self.mock_service = peer_grading_service.MockPeerGradingService() - self.system = LmsModuleSystem( - static_url=settings.STATIC_URL, - track_function=None, - get_module=None, - render_template=render_to_string, - replace_urls=None, - s3_interface=test_util_open_ended.S3_INTERFACE, - open_ended_grading_interface=test_util_open_ended.OPEN_ENDED_GRADING_INTERFACE, - mixins=settings.XBLOCK_MIXINS, - error_descriptor_class=ErrorDescriptor, - descriptor_runtime=None, - ) - self.descriptor = peer_grading_module.PeerGradingDescriptor(self.system, field_data, ScopeIds(None, None, None, None)) - self.descriptor.xmodule_runtime = self.system - self.peer_module = self.descriptor - self.peer_module.peer_gs = self.mock_service - self.logout() - - def test_get_next_submission_success(self): - data = {'location': self.location_string} - - response = self.peer_module.get_next_submission(data) - content = response - - self.assertTrue(content['success']) - self.assertIsNotNone(content['submission_id']) - self.assertIsNotNone(content['prompt']) - self.assertIsNotNone(content['submission_key']) - self.assertIsNotNone(content['max_score']) - - def test_get_next_submission_missing_location(self): - data = {} - d = self.peer_module.get_next_submission(data) - self.assertFalse(d['success']) - self.assertEqual(d['error'], "Missing required keys: location") - - def test_save_grade_success(self): - data = { - 'rubric_scores[]': [0, 0], - 'location': self.location_string, - 'submission_id': 1, - 'submission_key': 'fake key', - 'score': 2, - 'feedback': 'feedback', - 'submission_flagged': 'false', - 'answer_unknown': 'false', - 'rubric_scores_complete': 'true' - } - - qdict = MagicMock() - - def fake_get_item(key): - return data[key] - - qdict.__getitem__.side_effect = fake_get_item - qdict.getlist = fake_get_item - qdict.keys = data.keys - - response = self.peer_module.save_grade(qdict) - - self.assertTrue(response['success']) - - def test_save_grade_missing_keys(self): - data = {} - d = self.peer_module.save_grade(data) - self.assertFalse(d['success']) - self.assertTrue(d['error'].find('Missing required keys:') > -1) - - def test_is_calibrated_success(self): - data = {'location': self.location_string} - response = self.peer_module.is_student_calibrated(data) - - self.assertTrue(response['success']) - self.assertTrue('calibrated' in response) - - def test_is_calibrated_failure(self): - data = {} - response = self.peer_module.is_student_calibrated(data) - self.assertFalse(response['success']) - self.assertFalse('calibrated' in response) - - def test_show_calibration_essay_success(self): - data = {'location': self.location_string} - - response = self.peer_module.show_calibration_essay(data) - - self.assertTrue(response['success']) - self.assertIsNotNone(response['submission_id']) - self.assertIsNotNone(response['prompt']) - self.assertIsNotNone(response['submission_key']) - self.assertIsNotNone(response['max_score']) - - def test_show_calibration_essay_missing_key(self): - data = {} - - response = self.peer_module.show_calibration_essay(data) - - self.assertFalse(response['success']) - self.assertEqual(response['error'], "Missing required keys: location") - - def test_save_calibration_essay_success(self): - data = { - 'rubric_scores[]': [0, 0], - 'location': self.location_string, - 'submission_id': 1, - 'submission_key': 'fake key', - 'score': 2, - 'feedback': 'feedback', - 'submission_flagged': 'false' - } - - qdict = MagicMock() - - def fake_get_item(key): - return data[key] - - qdict.__getitem__.side_effect = fake_get_item - qdict.getlist = fake_get_item - qdict.keys = data.keys - - response = self.peer_module.save_calibration_essay(qdict) - self.assertTrue(response['success']) - self.assertTrue('actual_score' in response) - - def test_save_calibration_essay_missing_keys(self): - data = {} - response = self.peer_module.save_calibration_essay(data) - self.assertFalse(response['success']) - self.assertTrue(response['error'].find('Missing required keys:') > -1) - self.assertFalse('actual_score' in response) - - def test_save_grade_with_long_feedback(self): - """ - Test if feedback is too long save_grade() should return error message. - """ - data = { - 'rubric_scores[]': [0, 0], - 'location': self.location_string, - 'submission_id': 1, - 'submission_key': 'fake key', - 'score': 2, - 'feedback': '', - 'submission_flagged': 'false', - 'answer_unknown': 'false', - 'rubric_scores_complete': 'true' - } - - feedback_fragment = "This is very long feedback." - data["feedback"] = feedback_fragment * ( - (staff_grading_service.MAX_ALLOWED_FEEDBACK_LENGTH / len(feedback_fragment) + 1) - ) - - response_dict = self.peer_module.save_grade(data) - - # Should not succeed. - self.assertEquals(response_dict['success'], False) - self.assertEquals( - response_dict['error'], - "Feedback is too long, Max length is {0} characters.".format( - staff_grading_service.MAX_ALLOWED_FEEDBACK_LENGTH - ) - ) - - -class TestPanel(ModuleStoreTestCase): - """ - Run tests on the open ended panel - """ - def setUp(self): - super(TestPanel, self).setUp() - self.user = factories.UserFactory() - store = modulestore() - course_items = import_course_from_xml(store, self.user.id, TEST_DATA_DIR, ['open_ended']) # pylint: disable=maybe-no-member - self.course = course_items[0] - self.course_key = self.course.id - - def test_open_ended_panel(self): - """ - Test to see if the peer grading module in the demo course is found - @return: - """ - found_module, peer_grading_module = views.find_peer_grading_module(self.course) - self.assertTrue(found_module) - - @patch( - 'open_ended_grading.utils.create_controller_query_service', - Mock( - return_value=controller_query_service.MockControllerQueryService( - settings.OPEN_ENDED_GRADING_INTERFACE, - utils.render_to_string - ) - ) - ) - def test_problem_list(self): - """ - Ensure that the problem list from the grading controller server can be rendered properly locally - @return: - """ - request = RequestFactory().get( - reverse("open_ended_problems", kwargs={'course_id': self.course_key}) - ) - request.user = self.user - - mako_middleware_process_request(request) - response = views.student_problem_list(request, self.course.id.to_deprecated_string()) - self.assertRegexpMatches(response.content, "Here is a list of open ended problems for this course.") - - -class TestPeerGradingFound(ModuleStoreTestCase): - """ - Test to see if peer grading modules can be found properly. - """ - def setUp(self): - super(TestPeerGradingFound, self).setUp() - self.user = factories.UserFactory() - store = modulestore() - course_items = import_course_from_xml(store, self.user.id, TEST_DATA_DIR, ['open_ended_nopath']) # pylint: disable=maybe-no-member - self.course = course_items[0] - self.course_key = self.course.id - - def test_peer_grading_nopath(self): - """ - The open_ended_nopath course contains a peer grading module with no path to it. - Ensure that the exception is caught. - """ - - found, url = views.find_peer_grading_module(self.course) - self.assertEqual(found, False) - - -class TestStudentProblemList(ModuleStoreTestCase): - """ - Test if the student problem list correctly fetches and parses problems. - """ - def setUp(self): - super(TestStudentProblemList, self).setUp() - - # Load an open ended course with several problems. - self.user = factories.UserFactory() - store = modulestore() - course_items = import_course_from_xml(store, self.user.id, TEST_DATA_DIR, ['open_ended']) # pylint: disable=maybe-no-member - self.course = course_items[0] - self.course_key = self.course.id - - # Enroll our user in our course and make them an instructor. - make_instructor(self.course, self.user.email) - - @patch( - 'open_ended_grading.utils.create_controller_query_service', - Mock(return_value=StudentProblemListMockQuery()) - ) - def test_get_problem_list(self): - """ - Test to see if the StudentProblemList class can get and parse a problem list from ORA. - Mock the get_grading_status_list function using StudentProblemListMockQuery. - """ - # Initialize a StudentProblemList object. - student_problem_list = utils.StudentProblemList(self.course.id, unique_id_for_user(self.user)) - # Get the initial problem list from ORA. - success = student_problem_list.fetch_from_grading_service() - # Should be successful, and we should have three problems. See mock class for details. - self.assertTrue(success) - self.assertEqual(len(student_problem_list.problem_list), 3) - - # See if the problem locations are valid. - valid_problems = student_problem_list.add_problem_data(reverse('courses')) - # One location is invalid, so we should now have two. - self.assertEqual(len(valid_problems), 2) - # Ensure that human names are being set properly. - self.assertEqual(valid_problems[0]['grader_type_display_name'], "Instructor Assessment") - - -@ddt.ddt -class TestTabs(ModuleStoreTestCase): - """ - Test tabs. - """ - def setUp(self): - super(TestTabs, self).setUp() - self.course = CourseFactory(advanced_modules=('combinedopenended')) - self.addCleanup(lambda: self._enable_xblock_disable_config(False)) - - def _enable_xblock_disable_config(self, enabled): - """ Enable or disable xblocks disable. """ - config = XBlockDisableConfig.current() - config.enabled = enabled - config.disabled_blocks = "\n".join(('combinedopenended', 'peergrading')) - config.save() - cache.clear() - - @ddt.data( - views.StaffGradingTab, - views.PeerGradingTab, - views.OpenEndedGradingTab, - ) - def test_tabs_enabled(self, tab): - self.assertTrue(tab.is_enabled(self.course)) - - @ddt.data( - views.StaffGradingTab, - views.PeerGradingTab, - views.OpenEndedGradingTab, - ) - def test_tabs_disabled(self, tab): - self._enable_xblock_disable_config(True) - self.assertFalse(tab.is_enabled(self.course)) diff --git a/lms/djangoapps/open_ended_grading/utils.py b/lms/djangoapps/open_ended_grading/utils.py deleted file mode 100644 index fac12a2cda..0000000000 --- a/lms/djangoapps/open_ended_grading/utils.py +++ /dev/null @@ -1,171 +0,0 @@ -import logging -from urllib import urlencode - -from xmodule.modulestore import search -from xmodule.modulestore.django import modulestore -from xmodule.modulestore.exceptions import ItemNotFoundError, NoPathToItem -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 - -from edxmako.shortcuts import render_to_string - - -log = logging.getLogger(__name__) - -GRADER_DISPLAY_NAMES = { - 'ML': _("AI Assessment"), - 'PE': _("Peer Assessment"), - 'NA': _("Not yet available"), - 'BC': _("Automatic Checker"), - 'IN': _("Instructor Assessment"), -} - -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 - """ - activate_block_id = problem_url_parts[-1] - problem_url_parts = problem_url_parts[0:-1] - problem_url = base_course_url + "/" - for i, part in enumerate(problem_url_parts): - if part is not None: - # 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. - if i == 1: - problem_url += "courseware/" - problem_url += part + "/" - problem_url += '?{}'.format(urlencode({'activate_block_id': unicode(activate_block_id)})) - return problem_url - - -def does_location_exist(usage_key): - """ - 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: - search.path_to_location(modulestore(), usage_key) - return True - except ItemNotFoundError: - # If the problem cannot be found at the location received from the grading controller server, - # it has been deleted by the course author. - return False - except NoPathToItem: - # If the problem can be found, but there is no path to it, then we assume it is a draft. - # 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) - return False - - -def create_controller_query_service(): - """ - Return an instance of a service that can query edX ORA. - """ - return ControllerQueryService(settings.OPEN_ENDED_GRADING_INTERFACE, render_to_string) - - -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 - problem_list_dict = self.controller_qs.get_grading_status_list(self.course_id, self.user_id) - 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. - usage_key = self.course_id.make_usage_key_from_deprecated_string(problem['location']) - problem_url_parts = search.path_to_location(modulestore(), usage_key) - 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 diff --git a/lms/djangoapps/open_ended_grading/views.py b/lms/djangoapps/open_ended_grading/views.py deleted file mode 100644 index 8a0dc4ab9f..0000000000 --- a/lms/djangoapps/open_ended_grading/views.py +++ /dev/null @@ -1,401 +0,0 @@ -import logging - -from django.views.decorators.cache import cache_control -from edxmako.shortcuts import render_to_response -from django.core.urlresolvers import reverse - - -from courseware.courses import get_course_with_access -from courseware.access import has_access -from courseware.tabs import EnrolledTab - -from xmodule.open_ended_grading_classes.grading_service_module import GradingServiceError -import json -from student.models import unique_id_for_user - -from open_ended_grading import open_ended_notifications - -from xmodule.modulestore.django import modulestore -from xmodule.modulestore import search -from opaque_keys.edx.locations import SlashSeparatedCourseKey -from xmodule.modulestore.exceptions import NoPathToItem - -from django.http import HttpResponse, Http404, HttpResponseRedirect -from django.utils.translation import ugettext as _ - -from open_ended_grading.utils import ( - STAFF_ERROR_MESSAGE, StudentProblemList, generate_problem_url, create_controller_query_service -) -from xblock_django.models import XBlockDisableConfig - -log = logging.getLogger(__name__) - - -def _reverse_with_slash(url_name, course_key): - """ - Reverses the URL given the name and the course id, and then adds a trailing slash if - it does not exist yet. - @param url_name: The name of the url (eg 'staff_grading'). - @param course_id: The id of the course object (eg course.id). - @returns: The reversed url with a trailing slash. - """ - ajax_url = _reverse_without_slash(url_name, course_key) - if not ajax_url.endswith('/'): - ajax_url += '/' - return ajax_url - - -def _reverse_without_slash(url_name, course_key): - course_id = course_key.to_deprecated_string() - ajax_url = reverse(url_name, kwargs={'course_id': course_id}) - return ajax_url - - -DESCRIPTION_DICT = { - 'Peer Grading': _("View all problems that require peer assessment in this particular course."), - 'Staff Grading': _("View ungraded submissions submitted by students for the open ended problems in the course."), - 'Problems you have submitted': _("View open ended problems that you have previously submitted for grading."), - 'Flagged Submissions': _("View submissions that have been flagged by students as inappropriate."), -} - -ALERT_DICT = { - 'Peer Grading': _("New submissions to grade"), - 'Staff Grading': _("New submissions to grade"), - 'Problems you have submitted': _("New grades have been returned"), - 'Flagged Submissions': _("Submissions have been flagged for review"), -} - - -class StaffGradingTab(EnrolledTab): - """ - A tab for staff grading. - """ - type = 'staff_grading' - title = _("Staff grading") - view_name = "staff_grading" - - @classmethod - def is_enabled(cls, course, user=None): - if XBlockDisableConfig.is_block_type_disabled('combinedopenended'): - return False - if user and not has_access(user, 'staff', course, course.id): - return False - return "combinedopenended" in course.advanced_modules - - -class PeerGradingTab(EnrolledTab): - """ - A tab for peer grading. - """ - type = 'peer_grading' - # Translators: "Peer grading" appears on a tab that allows - # students to view open-ended problems that require grading - title = _("Peer grading") - view_name = "peer_grading" - - @classmethod - def is_enabled(cls, course, user=None): - if XBlockDisableConfig.is_block_type_disabled('combinedopenended'): - return False - if not super(PeerGradingTab, cls).is_enabled(course, user=user): - return False - return "combinedopenended" in course.advanced_modules - - -class OpenEndedGradingTab(EnrolledTab): - """ - A tab for open ended grading. - """ - type = 'open_ended' - # Translators: "Open Ended Panel" appears on a tab that, when clicked, opens up a panel that - # displays information about open-ended problems that a user has submitted or needs to grade - title = _("Open Ended Panel") - view_name = "open_ended_notifications" - - @classmethod - def is_enabled(cls, course, user=None): - if XBlockDisableConfig.is_block_type_disabled('combinedopenended'): - return False - if not super(OpenEndedGradingTab, cls).is_enabled(course, user=user): - return False - return "combinedopenended" in course.advanced_modules - - -@cache_control(no_cache=True, no_store=True, must_revalidate=True) -def staff_grading(request, course_id): - """ - Show the instructor grading interface. - """ - course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) - course = get_course_with_access(request.user, 'staff', course_key) - - ajax_url = _reverse_with_slash('staff_grading', course_key) - - return render_to_response('instructor/staff_grading.html', { - 'course': course, - 'course_id': course_id, - 'ajax_url': ajax_url, - # Checked above - 'staff_access': True, }) - - -def find_peer_grading_module(course): - """ - Given a course, finds the first peer grading module in it. - @param course: A course object. - @return: boolean found_module, string problem_url - """ - - # Reverse the base course url. - base_course_url = reverse('courses') - found_module = False - problem_url = "" - - # Get the peer grading modules currently in the course. Explicitly specify the course id to avoid issues with different runs. - items = modulestore().get_items(course.id, qualifiers={'category': 'peergrading'}) - # See if any of the modules are centralized modules (ie display info from multiple problems) - items = [i for i in items if not getattr(i, "use_for_single_location", True)] - # Loop through all potential peer grading modules, and find the first one that has a path to it. - for item in items: - # Generate a url for the first module and redirect the user to it. - try: - problem_url_parts = search.path_to_location(modulestore(), item.location) - except NoPathToItem: - # In the case of nopathtoitem, the peer grading module that was found is in an invalid state, and - # can no longer be accessed. Log an informational message, but this will not impact normal behavior. - log.info(u"Invalid peer grading module location %s in course %s. This module may need to be removed.", item.location, course.id) - continue - problem_url = generate_problem_url(problem_url_parts, base_course_url) - found_module = True - - return found_module, problem_url - - -@cache_control(no_cache=True, no_store=True, must_revalidate=True) -def peer_grading(request, course_id): - ''' - When a student clicks on the "peer grading" button in the open ended interface, link them to a peer grading - xmodule in the course. - ''' - course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) - #Get the current course - course = get_course_with_access(request.user, 'load', course_key) - - found_module, problem_url = find_peer_grading_module(course) - if not found_module: - error_message = _(""" - Error with initializing peer grading. - There has not been a peer grading module created in the courseware that would allow you to grade others. - Please check back later for this. - """) - log.exception(error_message + u"Current course is: {0}".format(course_id)) - return HttpResponse(error_message) - - return HttpResponseRedirect(problem_url) - - -@cache_control(no_cache=True, no_store=True, must_revalidate=True) -def student_problem_list(request, course_id): - """ - Show a list of problems they have attempted to a student. - Fetch the list from the grading controller server and append some data. - @param request: The request object for this view. - @param course_id: The id of the course to get the problem list for. - @return: Renders an HTML problem list table. - """ - assert isinstance(course_id, basestring) - course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) - # Load the course. Don't catch any errors here, as we want them to be loud. - course = get_course_with_access(request.user, 'load', course_key) - - # The anonymous student id is needed for communication with ORA. - student_id = unique_id_for_user(request.user) - base_course_url = reverse('courses') - error_text = "" - - student_problem_list = StudentProblemList(course_key, student_id) - # Get the problem list from ORA. - success = student_problem_list.fetch_from_grading_service() - # If we fetched the problem list properly, add in additional problem data. - if success: - # Add in links to problems. - valid_problems = student_problem_list.add_problem_data(base_course_url) - else: - # Get an error message to show to the student. - valid_problems = [] - error_text = student_problem_list.error_text - - ajax_url = _reverse_with_slash('open_ended_problems', course_key) - - context = { - 'course': course, - 'course_id': course_key.to_deprecated_string(), - 'ajax_url': ajax_url, - 'success': success, - 'problem_list': valid_problems, - 'error_text': error_text, - # Checked above - 'staff_access': False, - } - - return render_to_response('open_ended_problems/open_ended_problems.html', context) - - -@cache_control(no_cache=True, no_store=True, must_revalidate=True) -def flagged_problem_list(request, course_id): - ''' - Show a student problem list - ''' - course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) - course = get_course_with_access(request.user, 'staff', course_key) - - # call problem list service - success = False - error_text = "" - problem_list = [] - - # Make a service that can query edX ORA. - controller_qs = create_controller_query_service() - try: - problem_list_dict = controller_qs.get_flagged_problem_list(course_key) - success = problem_list_dict['success'] - if 'error' in problem_list_dict: - error_text = problem_list_dict['error'] - problem_list = [] - else: - problem_list = problem_list_dict['flagged_submissions'] - - except GradingServiceError: - #This is a staff_facing_error - error_text = STAFF_ERROR_MESSAGE - #This is a dev_facing_error - log.error("Could not get flagged problem list from external grading service for open ended.") - success = False - # catch error if if the json loads fails - except ValueError: - #This is a staff_facing_error - error_text = STAFF_ERROR_MESSAGE - #This is a dev_facing_error - log.error("Could not parse problem list from external grading service response.") - success = False - - ajax_url = _reverse_with_slash('open_ended_flagged_problems', course_key) - context = { - 'course': course, - 'course_id': course_id, - 'ajax_url': ajax_url, - 'success': success, - 'problem_list': problem_list, - 'error_text': error_text, - # Checked above - 'staff_access': True, - } - return render_to_response('open_ended_problems/open_ended_flagged_problems.html', context) - - -@cache_control(no_cache=True, no_store=True, must_revalidate=True) -def combined_notifications(request, course_id): - """ - Gets combined notifications from the grading controller and displays them - """ - course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) - course = get_course_with_access(request.user, 'load', course_key) - user = request.user - notifications = open_ended_notifications.combined_notifications(course, user) - response = notifications['response'] - notification_tuples = open_ended_notifications.NOTIFICATION_TYPES - - notification_list = [] - for response_num in xrange(len(notification_tuples)): - tag = notification_tuples[response_num][0] - if tag in response: - url_name = notification_tuples[response_num][1] - human_name = notification_tuples[response_num][2] - url = _reverse_without_slash(url_name, course_key) - has_img = response[tag] - - # check to make sure we have descriptions and alert messages - if human_name in DESCRIPTION_DICT: - description = DESCRIPTION_DICT[human_name] - else: - description = "" - - if human_name in ALERT_DICT: - alert_message = ALERT_DICT[human_name] - else: - alert_message = "" - - notification_item = { - 'url': url, - 'name': human_name, - 'alert': has_img, - 'description': description, - 'alert_message': alert_message - } - #The open ended panel will need to link the "peer grading" button in the panel to a peer grading - #xmodule defined in the course. This checks to see if the human name of the server notification - #that we are currently processing is "peer grading". If it is, it looks for a peer grading - #module in the course. If none exists, it removes the peer grading item from the panel. - if human_name == "Peer Grading": - found_module, problem_url = find_peer_grading_module(course) - if found_module: - notification_list.append(notification_item) - else: - notification_list.append(notification_item) - - ajax_url = _reverse_with_slash('open_ended_notifications', course_key) - combined_dict = { - 'error_text': "", - 'notification_list': notification_list, - 'course': course, - 'success': True, - 'ajax_url': ajax_url, - } - - return render_to_response('open_ended_problems/combined_notifications.html', combined_dict) - - -@cache_control(no_cache=True, no_store=True, must_revalidate=True) -def take_action_on_flags(request, course_id): - """ - Takes action on student flagged submissions. - Currently, only support unflag and ban actions. - """ - course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) - if request.method != 'POST': - raise Http404 - - required = ['submission_id', 'action_type', 'student_id'] - for key in required: - if key not in request.POST: - error_message = u'Missing key {0} from submission. Please reload and try again.'.format(key) - response = { - 'success': False, - 'error': STAFF_ERROR_MESSAGE + error_message - } - return HttpResponse(json.dumps(response), content_type="application/json") - - p = request.POST - submission_id = p['submission_id'] - action_type = p['action_type'] - student_id = p['student_id'] - student_id = student_id.strip(' \t\n\r') - submission_id = submission_id.strip(' \t\n\r') - action_type = action_type.lower().strip(' \t\n\r') - - # Make a service that can query edX ORA. - controller_qs = create_controller_query_service() - try: - response = controller_qs.take_action_on_flags(course_key, student_id, submission_id, action_type) - return HttpResponse(json.dumps(response), content_type="application/json") - except GradingServiceError: - log.exception( - u"Error taking action on flagged peer grading submissions, " - u"submission_id: {0}, action_type: {1}, grader_id: {2}" - .format(submission_id, action_type, student_id) - ) - response = { - 'success': False, - 'error': STAFF_ERROR_MESSAGE - } - return HttpResponse(json.dumps(response), content_type="application/json") diff --git a/lms/envs/common.py b/lms/envs/common.py index 1a948c8a3b..5413bf9bfe 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -1029,26 +1029,6 @@ PAID_COURSE_REGISTRATION_CURRENCY = ['usd', '$'] # Members of this group are allowed to generate payment reports PAYMENT_REPORT_GENERATOR_GROUP = 'shoppingcart_report_access' -################################# open ended grading config ##################### - -#By setting up the default settings with an incorrect user name and password, -# will get an error when attempting to connect -OPEN_ENDED_GRADING_INTERFACE = { - 'url': 'http://example.com/peer_grading', - 'username': 'incorrect_user', - 'password': 'incorrect_pass', - 'staff_grading': 'staff_grading', - 'peer_grading': 'peer_grading', - 'grading_controller': 'grading_controller' -} - -# Used for testing, debugging peer grading -MOCK_PEER_GRADING = False - -# Used for testing, debugging staff grading -MOCK_STAFF_GRADING = False - - ################################# EdxNotes config ######################### # Configure the LMS to use our stub EdxNotes implementation @@ -1828,7 +1808,6 @@ INSTALLED_APPS = ( 'dashboard', 'instructor', 'instructor_task', - 'open_ended_grading', 'openedx.core.djangoapps.course_groups', 'bulk_email', 'branding', diff --git a/lms/urls.py b/lms/urls.py index 33d8237d48..1e50b236a7 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -549,61 +549,6 @@ urlpatterns += ( ), # see ENABLE_INSTRUCTOR_LEGACY_DASHBOARD section for legacy dash urls - # Open Ended grading views - url( - r'^courses/{}/staff_grading$'.format( - settings.COURSE_ID_PATTERN, - ), - 'open_ended_grading.views.staff_grading', - name='staff_grading', - ), - url( - r'^courses/{}/staff_grading/get_next$'.format( - settings.COURSE_ID_PATTERN, - ), - 'open_ended_grading.staff_grading_service.get_next', - name='staff_grading_get_next', - ), - url( - r'^courses/{}/staff_grading/save_grade$'.format( - settings.COURSE_ID_PATTERN, - ), - 'open_ended_grading.staff_grading_service.save_grade', - name='staff_grading_save_grade', - ), - url( - r'^courses/{}/staff_grading/get_problem_list$'.format( - settings.COURSE_ID_PATTERN, - ), - 'open_ended_grading.staff_grading_service.get_problem_list', - name='staff_grading_get_problem_list', - ), - - # Open Ended problem list - url( - r'^courses/{}/open_ended_problems$'.format( - settings.COURSE_ID_PATTERN, - ), - 'open_ended_grading.views.student_problem_list', - name='open_ended_problems', - ), - - # Open Ended flagged problem list - url( - r'^courses/{}/open_ended_flagged_problems$'.format( - settings.COURSE_ID_PATTERN, - ), - 'open_ended_grading.views.flagged_problem_list', - name='open_ended_flagged_problems', - ), - url( - r'^courses/{}/open_ended_flagged_problems/take_action_on_flags$'.format( - settings.COURSE_ID_PATTERN, - ), - 'open_ended_grading.views.take_action_on_flags', - name='open_ended_flagged_problems_take_action', - ), - # Cohorts management url( r'^courses/{}/cohorts/settings$'.format( @@ -655,23 +600,6 @@ urlpatterns += ( name='cohort_discussion_topics', ), - # Open Ended Notifications - url( - r'^courses/{}/open_ended_notifications$'.format( - settings.COURSE_ID_PATTERN, - ), - 'open_ended_grading.views.combined_notifications', - name='open_ended_notifications', - ), - - url( - r'^courses/{}/peer_grading$'.format( - settings.COURSE_ID_PATTERN, - ), - 'open_ended_grading.views.peer_grading', - name='peer_grading', - ), - url( r'^courses/{}/notes$'.format( settings.COURSE_ID_PATTERN,