From ecf04b3e493ff5c9c3f14de98f691cb3ec086576 Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Fri, 28 Dec 2012 11:29:29 -0500 Subject: [PATCH] Refactor existing grading logic into a new app. --- lms/djangoapps/instructor/views.py | 21 ------ lms/djangoapps/open_ended_grading/__init__.py | 0 .../open_ended_grading/grading_service.py | 71 +++++++++++++++++++ .../peer_grading_service.py | 15 ++++ .../staff_grading.py} | 0 .../staff_grading_service.py | 69 +++--------------- lms/djangoapps/open_ended_grading/tests.py | 16 +++++ lms/djangoapps/open_ended_grading/views.py | 57 +++++++++++++++ lms/envs/common.py | 1 + lms/urls.py | 10 +-- 10 files changed, 176 insertions(+), 84 deletions(-) create mode 100644 lms/djangoapps/open_ended_grading/__init__.py create mode 100644 lms/djangoapps/open_ended_grading/grading_service.py create mode 100644 lms/djangoapps/open_ended_grading/peer_grading_service.py rename lms/djangoapps/{instructor/grading.py => open_ended_grading/staff_grading.py} (100%) rename lms/djangoapps/{instructor => open_ended_grading}/staff_grading_service.py (84%) create mode 100644 lms/djangoapps/open_ended_grading/tests.py create mode 100644 lms/djangoapps/open_ended_grading/views.py diff --git a/lms/djangoapps/instructor/views.py b/lms/djangoapps/instructor/views.py index 79cf0caaf3..2bad058ad8 100644 --- a/lms/djangoapps/instructor/views.py +++ b/lms/djangoapps/instructor/views.py @@ -28,7 +28,6 @@ from xmodule.modulestore.exceptions import InvalidLocationError, ItemNotFoundErr from xmodule.modulestore.search import path_to_location import track.views -from .grading import StaffGrading log = logging.getLogger(__name__) @@ -414,26 +413,6 @@ def get_student_grade_summary_data(request, course, course_id, get_grades=True, -@cache_control(no_cache=True, no_store=True, must_revalidate=True) -def staff_grading(request, course_id): - """ - Show the instructor grading interface. - """ - course = get_course_with_access(request.user, course_id, 'staff') - - grading = StaffGrading(course) - - ajax_url = reverse('staff_grading', kwargs={'course_id': course_id}) - if not ajax_url.endswith('/'): - ajax_url += '/' - - return render_to_response('instructor/staff_grading.html', { - 'view_html': grading.get_html(), - 'course': course, - 'course_id': course_id, - 'ajax_url': ajax_url, - # Checked above - 'staff_access': True, }) @cache_control(no_cache=True, no_store=True, must_revalidate=True) diff --git a/lms/djangoapps/open_ended_grading/__init__.py b/lms/djangoapps/open_ended_grading/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lms/djangoapps/open_ended_grading/grading_service.py b/lms/djangoapps/open_ended_grading/grading_service.py new file mode 100644 index 0000000000..be15ae08ee --- /dev/null +++ b/lms/djangoapps/open_ended_grading/grading_service.py @@ -0,0 +1,71 @@ +# This class gives a common interface for logging into +# the graing controller +import json +import logging +import requests +from requests.exceptions import RequestException, ConnectionError, HTTPError +import sys + +from django.conf import settings +from django.http import HttpResponse, Http404 + +from courseware.access import has_access +from util.json_request import expect_json +from xmodule.course_module import CourseDescriptor + +log = logging.getLogger(__name__) + +class GradingServiceError(Exception): + pass + +class GradingService(object): + """ + Interface to staff grading backend. + """ + def __init__(self, config): + self.username = config['username'] + self.password = config['password'] + self.url = config['url'] + self.login_url = self.url + '/login/' + self.session = requests.session() + + def _login(self): + """ + Log into the staff grading service. + + Raises requests.exceptions.HTTPError if something goes wrong. + + Returns the decoded json dict of the response. + """ + response = self.session.post(self.login_url, + {'username': self.username, + 'password': self.password,}) + + response.raise_for_status() + + return response.json + + + def _try_with_login(self, operation): + """ + Call operation(), which should return a requests response object. If + the request fails with a 'login_required' error, call _login() and try + the operation again. + + Returns the result of operation(). Does not catch exceptions. + """ + response = operation() + if (response.json + and response.json.get('success') == False + and response.json.get('error') == 'login_required'): + # apparrently we aren't logged in. Try to fix that. + r = self._login() + if r and not r.get('success'): + log.warning("Couldn't log into staff_grading backend. Response: %s", + r) + # try again + response = operation() + response.raise_for_status() + + return response + diff --git a/lms/djangoapps/open_ended_grading/peer_grading_service.py b/lms/djangoapps/open_ended_grading/peer_grading_service.py new file mode 100644 index 0000000000..cad23a072c --- /dev/null +++ b/lms/djangoapps/open_ended_grading/peer_grading_service.py @@ -0,0 +1,15 @@ +import json +import logging +import requests +from requests.exceptions import RequestException, ConnectionError, HTTPError +import sys + +from django.conf import settings +from django.http import HttpResponse, Http404 + +from courseware.access import has_access +from util.json_request import expect_json +from xmodule.course_module import CourseDescriptor + +log = logging.getLogger(__name__) + diff --git a/lms/djangoapps/instructor/grading.py b/lms/djangoapps/open_ended_grading/staff_grading.py similarity index 100% rename from lms/djangoapps/instructor/grading.py rename to lms/djangoapps/open_ended_grading/staff_grading.py diff --git a/lms/djangoapps/instructor/staff_grading_service.py b/lms/djangoapps/open_ended_grading/staff_grading_service.py similarity index 84% rename from lms/djangoapps/instructor/staff_grading_service.py rename to lms/djangoapps/open_ended_grading/staff_grading_service.py index ea8f0de074..6d0cea983b 100644 --- a/lms/djangoapps/instructor/staff_grading_service.py +++ b/lms/djangoapps/open_ended_grading/staff_grading_service.py @@ -7,6 +7,8 @@ import logging import requests from requests.exceptions import RequestException, ConnectionError, HTTPError import sys +from grading_service import GradingService +from grading_service import GradingServiceError from django.conf import settings from django.http import HttpResponse, Http404 @@ -18,9 +20,6 @@ from xmodule.course_module import CourseDescriptor log = logging.getLogger(__name__) -class GradingServiceError(Exception): - pass - class MockStaffGradingService(object): """ @@ -57,62 +56,16 @@ class MockStaffGradingService(object): return self.get_next(course_id, 'fake location', grader_id) -class StaffGradingService(object): +class StaffGradingService(GradingService): """ Interface to staff grading backend. """ def __init__(self, config): - self.username = config['username'] - self.password = config['password'] - self.url = config['url'] - - self.login_url = self.url + '/login/' + super(StaffGradingService, self).__init__(config) self.get_next_url = self.url + '/get_next_submission/' self.save_grade_url = self.url + '/save_grade/' self.get_problem_list_url = self.url + '/get_problem_list/' - self.session = requests.session() - - - def _login(self): - """ - Log into the staff grading service. - - Raises requests.exceptions.HTTPError if something goes wrong. - - Returns the decoded json dict of the response. - """ - response = self.session.post(self.login_url, - {'username': self.username, - 'password': self.password,}) - - response.raise_for_status() - - return response.json - - - def _try_with_login(self, operation): - """ - Call operation(), which should return a requests response object. If - the request fails with a 'login_required' error, call _login() and try - the operation again. - - Returns the result of operation(). Does not catch exceptions. - """ - response = operation() - if (response.json - and response.json.get('success') == False - and response.json.get('error') == 'login_required'): - # apparrently we aren't logged in. Try to fix that. - r = self._login() - if r and not r.get('success'): - log.warning("Couldn't log into staff_grading backend. Response: %s", - r) - # try again - response = operation() - response.raise_for_status() - - return response def get_problem_list(self, course_id, grader_id): """ @@ -203,11 +156,11 @@ class StaffGradingService(object): return r.text -# don't initialize until grading_service() is called--means that just +# don't initialize until staff_grading_service() is called--means that just # importing this file doesn't create objects that may not have the right config _service = None -def grading_service(): +def staff_grading_service(): """ Return a staff grading service instance--if settings.MOCK_STAFF_GRADING is True, returns a mock one, otherwise a real one. @@ -308,12 +261,12 @@ def get_problem_list(request, course_id): """ _check_access(request.user, course_id) try: - response = grading_service().get_problem_list(course_id, request.user.id) + response = staff_grading_service().get_problem_list(course_id, request.user.id) return HttpResponse(response, mimetype="application/json") except GradingServiceError: log.exception("Error from grading service. server url: {0}" - .format(grading_service().url)) + .format(staff_grading_service().url)) return HttpResponse(json.dumps({'success': False, 'error': 'Could not connect to grading service'})) @@ -323,10 +276,10 @@ def _get_next(course_id, grader_id, location): Implementation of get_next (also called from save_grade) -- returns a json string """ try: - return grading_service().get_next(course_id, location, grader_id) + return staff_grading_service().get_next(course_id, location, grader_id) except GradingServiceError: log.exception("Error from grading service. server url: {0}" - .format(grading_service().url)) + .format(staff_grading_service().url)) return json.dumps({'success': False, 'error': 'Could not connect to grading service'}) @@ -364,7 +317,7 @@ def save_grade(request, course_id): location = p['location'] skipped = 'skipped' in p try: - result_json = grading_service().save_grade(course_id, + result_json = staff_grading_service().save_grade(course_id, grader_id, p['submission_id'], p['score'], diff --git a/lms/djangoapps/open_ended_grading/tests.py b/lms/djangoapps/open_ended_grading/tests.py new file mode 100644 index 0000000000..501deb776c --- /dev/null +++ b/lms/djangoapps/open_ended_grading/tests.py @@ -0,0 +1,16 @@ +""" +This file demonstrates writing tests using the unittest module. These will pass +when you run "manage.py test". + +Replace this with more appropriate tests for your application. +""" + +from django.test import TestCase + + +class SimpleTest(TestCase): + def test_basic_addition(self): + """ + Tests that 1 + 1 always equals 2. + """ + self.assertEqual(1 + 1, 2) diff --git a/lms/djangoapps/open_ended_grading/views.py b/lms/djangoapps/open_ended_grading/views.py new file mode 100644 index 0000000000..9066f8323a --- /dev/null +++ b/lms/djangoapps/open_ended_grading/views.py @@ -0,0 +1,57 @@ +# Grading Views + +from collections import defaultdict +import csv +import logging +import os +import urllib + +from django.conf import settings +from django.contrib.auth.models import User, Group +from django.http import HttpResponse +from django_future.csrf import ensure_csrf_cookie +from django.views.decorators.cache import cache_control +from mitxmako.shortcuts import render_to_response +from django.core.urlresolvers import reverse + +from courseware import grades +from courseware.access import has_access, get_access_group_name +from courseware.courses import get_course_with_access +from django_comment_client.models import Role, FORUM_ROLE_ADMINISTRATOR, FORUM_ROLE_MODERATOR, FORUM_ROLE_COMMUNITY_TA +from django_comment_client.utils import has_forum_access +from psychometrics import psychoanalyze +from student.models import CourseEnrollment +from xmodule.course_module import CourseDescriptor +from xmodule.modulestore import Location +from xmodule.modulestore.django import modulestore +from xmodule.modulestore.exceptions import InvalidLocationError, ItemNotFoundError, NoPathToItem +from xmodule.modulestore.search import path_to_location +import track.views + +from .staff_grading import StaffGrading + + +log = logging.getLogger(__name__) + +template_imports = {'urllib': urllib} +@cache_control(no_cache=True, no_store=True, must_revalidate=True) +def staff_grading(request, course_id): + """ + Show the instructor grading interface. + """ + course = get_course_with_access(request.user, course_id, 'staff') + + grading = StaffGrading(course) + + ajax_url = reverse('staff_grading', kwargs={'course_id': course_id}) + if not ajax_url.endswith('/'): + ajax_url += '/' + + return render_to_response('instructor/staff_grading.html', { + 'view_html': grading.get_html(), + 'course': course, + 'course_id': course_id, + 'ajax_url': ajax_url, + # Checked above + 'staff_access': True, }) + diff --git a/lms/envs/common.py b/lms/envs/common.py index 26941f7e01..1cf22a6323 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -600,6 +600,7 @@ INSTALLED_APPS = ( 'util', 'certificates', 'instructor', + 'open_ended_grading', 'psychometrics', 'licenses', diff --git a/lms/urls.py b/lms/urls.py index baa720028b..f04af88e72 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -241,15 +241,15 @@ if settings.COURSEWARE_ENABLED: url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/enroll_students$', 'instructor.views.enroll_students', name='enroll_students'), url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/staff_grading$', - 'instructor.views.staff_grading', name='staff_grading'), + 'open_ended_grading.views.staff_grading', name='staff_grading'), url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/staff_grading/get_next$', - 'instructor.staff_grading_service.get_next', name='staff_grading_get_next'), + 'open_ended_grading.staff_grading_service.get_next', name='staff_grading_get_next'), url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/staff_grading/save_grade$', - 'instructor.staff_grading_service.save_grade', name='staff_grading_save_grade'), + 'open_ended_grading.staff_grading_service.save_grade', name='staff_grading_save_grade'), url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/staff_grading/save_grade$', - 'instructor.staff_grading_service.save_grade', name='staff_grading_save_grade'), + 'open_ended_grading.staff_grading_service.save_grade', name='staff_grading_save_grade'), url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/staff_grading/get_problem_list$', - 'instructor.staff_grading_service.get_problem_list', name='staff_grading_get_problem_list'), + 'open_ended_grading.staff_grading_service.get_problem_list', name='staff_grading_get_problem_list'), ) # discussion forums live within courseware, so courseware must be enabled first