Refactor existing grading logic into a new app.
This commit is contained in:
@@ -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)
|
||||
|
||||
0
lms/djangoapps/open_ended_grading/__init__.py
Normal file
0
lms/djangoapps/open_ended_grading/__init__.py
Normal file
71
lms/djangoapps/open_ended_grading/grading_service.py
Normal file
71
lms/djangoapps/open_ended_grading/grading_service.py
Normal file
@@ -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
|
||||
|
||||
15
lms/djangoapps/open_ended_grading/peer_grading_service.py
Normal file
15
lms/djangoapps/open_ended_grading/peer_grading_service.py
Normal file
@@ -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__)
|
||||
|
||||
@@ -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'],
|
||||
16
lms/djangoapps/open_ended_grading/tests.py
Normal file
16
lms/djangoapps/open_ended_grading/tests.py
Normal file
@@ -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)
|
||||
57
lms/djangoapps/open_ended_grading/views.py
Normal file
57
lms/djangoapps/open_ended_grading/views.py
Normal file
@@ -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, })
|
||||
|
||||
@@ -600,6 +600,7 @@ INSTALLED_APPS = (
|
||||
'util',
|
||||
'certificates',
|
||||
'instructor',
|
||||
'open_ended_grading',
|
||||
'psychometrics',
|
||||
'licenses',
|
||||
|
||||
|
||||
10
lms/urls.py
10
lms/urls.py
@@ -241,15 +241,15 @@ if settings.COURSEWARE_ENABLED:
|
||||
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/enroll_students$',
|
||||
'instructor.views.enroll_students', name='enroll_students'),
|
||||
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$',
|
||||
'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$',
|
||||
'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$',
|
||||
'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$',
|
||||
'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
|
||||
|
||||
Reference in New Issue
Block a user