Commit ecf04b3e by Diana Huang

Refactor existing grading logic into a new app.

parent e4dff3f7
...@@ -28,7 +28,6 @@ from xmodule.modulestore.exceptions import InvalidLocationError, ItemNotFoundErr ...@@ -28,7 +28,6 @@ from xmodule.modulestore.exceptions import InvalidLocationError, ItemNotFoundErr
from xmodule.modulestore.search import path_to_location from xmodule.modulestore.search import path_to_location
import track.views import track.views
from .grading import StaffGrading
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
...@@ -414,26 +413,6 @@ def get_student_grade_summary_data(request, course, course_id, get_grades=True, ...@@ -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) @cache_control(no_cache=True, no_store=True, must_revalidate=True)
......
# 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
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__)
...@@ -7,6 +7,8 @@ import logging ...@@ -7,6 +7,8 @@ import logging
import requests import requests
from requests.exceptions import RequestException, ConnectionError, HTTPError from requests.exceptions import RequestException, ConnectionError, HTTPError
import sys import sys
from grading_service import GradingService
from grading_service import GradingServiceError
from django.conf import settings from django.conf import settings
from django.http import HttpResponse, Http404 from django.http import HttpResponse, Http404
...@@ -18,9 +20,6 @@ from xmodule.course_module import CourseDescriptor ...@@ -18,9 +20,6 @@ from xmodule.course_module import CourseDescriptor
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class GradingServiceError(Exception):
pass
class MockStaffGradingService(object): class MockStaffGradingService(object):
""" """
...@@ -57,62 +56,16 @@ class MockStaffGradingService(object): ...@@ -57,62 +56,16 @@ class MockStaffGradingService(object):
return self.get_next(course_id, 'fake location', grader_id) return self.get_next(course_id, 'fake location', grader_id)
class StaffGradingService(object): class StaffGradingService(GradingService):
""" """
Interface to staff grading backend. Interface to staff grading backend.
""" """
def __init__(self, config): def __init__(self, config):
self.username = config['username'] super(StaffGradingService, self).__init__(config)
self.password = config['password']
self.url = config['url']
self.login_url = self.url + '/login/'
self.get_next_url = self.url + '/get_next_submission/' self.get_next_url = self.url + '/get_next_submission/'
self.save_grade_url = self.url + '/save_grade/' self.save_grade_url = self.url + '/save_grade/'
self.get_problem_list_url = self.url + '/get_problem_list/' 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): def get_problem_list(self, course_id, grader_id):
""" """
...@@ -203,11 +156,11 @@ class StaffGradingService(object): ...@@ -203,11 +156,11 @@ class StaffGradingService(object):
return r.text 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 # importing this file doesn't create objects that may not have the right config
_service = None _service = None
def grading_service(): def staff_grading_service():
""" """
Return a staff grading service instance--if settings.MOCK_STAFF_GRADING is True, Return a staff grading service instance--if settings.MOCK_STAFF_GRADING is True,
returns a mock one, otherwise a real one. returns a mock one, otherwise a real one.
...@@ -308,12 +261,12 @@ def get_problem_list(request, course_id): ...@@ -308,12 +261,12 @@ def get_problem_list(request, course_id):
""" """
_check_access(request.user, course_id) _check_access(request.user, course_id)
try: 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, return HttpResponse(response,
mimetype="application/json") mimetype="application/json")
except GradingServiceError: except GradingServiceError:
log.exception("Error from grading service. server url: {0}" log.exception("Error from grading service. server url: {0}"
.format(grading_service().url)) .format(staff_grading_service().url))
return HttpResponse(json.dumps({'success': False, return HttpResponse(json.dumps({'success': False,
'error': 'Could not connect to grading service'})) 'error': 'Could not connect to grading service'}))
...@@ -323,10 +276,10 @@ def _get_next(course_id, grader_id, location): ...@@ -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 Implementation of get_next (also called from save_grade) -- returns a json string
""" """
try: try:
return grading_service().get_next(course_id, location, grader_id) return staff_grading_service().get_next(course_id, location, grader_id)
except GradingServiceError: except GradingServiceError:
log.exception("Error from grading service. server url: {0}" log.exception("Error from grading service. server url: {0}"
.format(grading_service().url)) .format(staff_grading_service().url))
return json.dumps({'success': False, return json.dumps({'success': False,
'error': 'Could not connect to grading service'}) 'error': 'Could not connect to grading service'})
...@@ -364,7 +317,7 @@ def save_grade(request, course_id): ...@@ -364,7 +317,7 @@ def save_grade(request, course_id):
location = p['location'] location = p['location']
skipped = 'skipped' in p skipped = 'skipped' in p
try: try:
result_json = grading_service().save_grade(course_id, result_json = staff_grading_service().save_grade(course_id,
grader_id, grader_id,
p['submission_id'], p['submission_id'],
p['score'], p['score'],
......
"""
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)
# 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, })
...@@ -600,6 +600,7 @@ INSTALLED_APPS = ( ...@@ -600,6 +600,7 @@ INSTALLED_APPS = (
'util', 'util',
'certificates', 'certificates',
'instructor', 'instructor',
'open_ended_grading',
'psychometrics', 'psychometrics',
'licenses', 'licenses',
......
...@@ -241,15 +241,15 @@ if settings.COURSEWARE_ENABLED: ...@@ -241,15 +241,15 @@ if settings.COURSEWARE_ENABLED:
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/enroll_students$', url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/enroll_students$',
'instructor.views.enroll_students', name='enroll_students'), 'instructor.views.enroll_students', name='enroll_students'),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/staff_grading$', url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/staff_grading$',
'instructor.views.staff_grading', name='staff_grading'), 'open_ended_grading.views.staff_grading', name='staff_grading'),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/staff_grading/get_next$', url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/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<course_id>[^/]+/[^/]+/[^/]+)/staff_grading/save_grade$', url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/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<course_id>[^/]+/[^/]+/[^/]+)/staff_grading/save_grade$', url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/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<course_id>[^/]+/[^/]+/[^/]+)/staff_grading/get_problem_list$', url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/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 # discussion forums live within courseware, so courseware must be enabled first
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment