Commit d0e2b85e by Victor Shnayder

Refactor testing code, hook up frontend.

- now getting requests from js to server and back, with mocked service.
parent ff119265
......@@ -222,24 +222,28 @@ class PageLoader(ActivateLoginTestCase):
def check_for_get_code(self, code, url):
"""
Check that we got the expected code when accessing url via GET.
Returns the response.
"""
resp = self.client.get(url)
self.assertEqual(resp.status_code, code,
"got code {0} for url '{1}'. Expected code {2}"
.format(resp.status_code, url, code))
return resp
def check_for_post_code(self, code, url, data={}):
"""
Check that we got the expected code when accessing url via POST.
Returns the response.
"""
resp = self.client.post(url, data)
self.assertEqual(resp.status_code, code,
"got code {0} for url '{1}'. Expected code {2}"
.format(resp.status_code, url, code))
return resp
def check_pages_load(self, course_name, data_dir, modstore):
"""Make all locations in course load"""
print "Checking course {0} in {1}".format(course_name, data_dir)
......@@ -661,46 +665,46 @@ class TestCourseGrader(PageLoader):
return [c for c in courses if c.id==course_id][0]
self.graded_course = find_course("edX/graded/2012_Fall")
# create a test student
self.student = 'view@test.com'
self.password = 'foo'
self.create_account('u1', self.student, self.password)
self.activate_user(self.student)
self.enroll(self.graded_course)
self.student_user = user(self.student)
self.factory = RequestFactory()
def get_grade_summary(self):
student_module_cache = StudentModuleCache.cache_for_descriptor_descendents(
self.graded_course.id, self.student_user, self.graded_course)
fake_request = self.factory.get(reverse('progress',
kwargs={'course_id': self.graded_course.id}))
return grades.grade(self.student_user, fake_request,
return grades.grade(self.student_user, fake_request,
self.graded_course, student_module_cache)
def get_homework_scores(self):
return self.get_grade_summary()['totaled_scores']['Homework']
def get_progress_summary(self):
student_module_cache = StudentModuleCache.cache_for_descriptor_descendents(
self.graded_course.id, self.student_user, self.graded_course)
fake_request = self.factory.get(reverse('progress',
kwargs={'course_id': self.graded_course.id}))
progress_summary = grades.progress_summary(self.student_user, fake_request,
progress_summary = grades.progress_summary(self.student_user, fake_request,
self.graded_course, student_module_cache)
return progress_summary
def check_grade_percent(self, percent):
grade_summary = self.get_grade_summary()
self.assertEqual(percent, grade_summary['percent'])
self.assertEqual(grade_summary['percent'], percent)
def submit_question_answer(self, problem_url_name, responses):
"""
The field names of a problem are hard to determine. This method only works
......@@ -710,96 +714,96 @@ class TestCourseGrader(PageLoader):
input_i4x-edX-graded-problem-H1P3_2_2
"""
problem_location = "i4x://edX/graded/problem/{0}".format(problem_url_name)
modx_url = reverse('modx_dispatch',
modx_url = reverse('modx_dispatch',
kwargs={
'course_id' : self.graded_course.id,
'location' : problem_location,
'dispatch' : 'problem_check', }
)
resp = self.client.post(modx_url, {
'input_i4x-edX-graded-problem-{0}_2_1'.format(problem_url_name): responses[0],
'input_i4x-edX-graded-problem-{0}_2_2'.format(problem_url_name): responses[1],
})
print "modx_url" , modx_url, "responses" , responses
print "resp" , resp
return resp
def problem_location(self, problem_url_name):
return "i4x://edX/graded/problem/{0}".format(problem_url_name)
def reset_question_answer(self, problem_url_name):
problem_location = self.problem_location(problem_url_name)
modx_url = reverse('modx_dispatch',
modx_url = reverse('modx_dispatch',
kwargs={
'course_id' : self.graded_course.id,
'location' : problem_location,
'dispatch' : 'problem_reset', }
)
resp = self.client.post(modx_url)
return resp
return resp
def test_get_graded(self):
#### Check that the grader shows we have 0% in the course
self.check_grade_percent(0)
#### Submit the answers to a few problems as ajax calls
def earned_hw_scores():
"""Global scores, each Score is a Problem Set"""
return [s.earned for s in self.get_homework_scores()]
def score_for_hw(hw_url_name):
hw_section = [section for section
in self.get_progress_summary()[0]['sections']
if section.get('url_name') == hw_url_name][0]
return [s.earned for s in hw_section['scores']]
# Only get half of the first problem correct
self.submit_question_answer('H1P1', ['Correct', 'Incorrect'])
self.check_grade_percent(0.06)
self.assertEqual(earned_hw_scores(), [1.0, 0, 0]) # Order matters
self.assertEqual(score_for_hw('Homework1'), [1.0, 0.0])
# Get both parts of the first problem correct
self.reset_question_answer('H1P1')
self.submit_question_answer('H1P1', ['Correct', 'Correct'])
self.check_grade_percent(0.13)
self.assertEqual(earned_hw_scores(), [2.0, 0, 0])
self.assertEqual(score_for_hw('Homework1'), [2.0, 0.0])
# This problem is shown in an ABTest
self.submit_question_answer('H1P2', ['Correct', 'Correct'])
self.check_grade_percent(0.25)
self.assertEqual(earned_hw_scores(), [4.0, 0.0, 0])
self.assertEqual(score_for_hw('Homework1'), [2.0, 2.0])
self.assertEqual(score_for_hw('Homework1'), [2.0, 2.0])
# This problem is hidden in an ABTest. Getting it correct doesn't change total grade
self.submit_question_answer('H1P3', ['Correct', 'Correct'])
self.check_grade_percent(0.25)
self.assertEqual(score_for_hw('Homework1'), [2.0, 2.0])
# On the second homework, we only answer half of the questions.
# Then it will be dropped when homework three becomes the higher percent
# This problem is also weighted to be 4 points (instead of default of 2)
# If the problem was unweighted the percent would have been 0.38 so we
# If the problem was unweighted the percent would have been 0.38 so we
# know it works.
self.submit_question_answer('H2P1', ['Correct', 'Correct'])
self.check_grade_percent(0.42)
self.assertEqual(earned_hw_scores(), [4.0, 4.0, 0])
self.assertEqual(earned_hw_scores(), [4.0, 4.0, 0])
# Third homework
self.submit_question_answer('H3P1', ['Correct', 'Correct'])
self.check_grade_percent(0.42) # Score didn't change
self.assertEqual(earned_hw_scores(), [4.0, 4.0, 2.0])
self.assertEqual(earned_hw_scores(), [4.0, 4.0, 2.0])
self.submit_question_answer('H3P2', ['Correct', 'Correct'])
self.check_grade_percent(0.5) # Now homework2 dropped. Score changes
self.assertEqual(earned_hw_scores(), [4.0, 4.0, 4.0])
self.assertEqual(earned_hw_scores(), [4.0, 4.0, 4.0])
# Now we answer the final question (worth half of the grade)
self.submit_question_answer('FinalQuestion', ['Correct', 'Correct'])
self.check_grade_percent(1.0) # Hooray! We got 100%
......
......@@ -21,6 +21,25 @@ log = logging.getLogger("mitx.courseware")
class GradingServiceError(Exception):
pass
class MockStaffGradingService(object):
"""
A simple mockup of a staff grading service, testing.
"""
def __init__(self):
self.cnt = 0
def get_next(self, course_id):
self.cnt += 1
return json.dumps({'success': True,
'submission_id': self.cnt,
'submission': 'Test submission {cnt}'.format(cnt=self.cnt),
'rubric': 'A rubric'})
def save_grade(self, course_id, submission_id, score, feedback):
return self.get_next(course_id)
class StaffGradingService(object):
"""
Interface to staff grading backend.
......@@ -30,20 +49,20 @@ class StaffGradingService(object):
# TODO: add auth
self.session = requests.session()
def get_next(course_id):
def get_next(self, course_id):
"""
Get the next thing to grade. Returns json, or raises GradingServiceError
if there's a problem.
"""
try:
r = self.session.get(url + 'get_next')
r = self.session.get(self.url + 'get_next')
except requests.exceptions.ConnectionError as err:
# reraise as promised GradingServiceError, but preserve stacktrace.
raise GradingServiceError, str(err), sys.exc_info()[2]
return r.text
def save_grade(course_id, submission_id, score, feedback):
def save_grade(self, course_id, submission_id, score, feedback):
"""
Save a grade.
......@@ -52,15 +71,15 @@ class StaffGradingService(object):
Returns json, or raises GradingServiceError if there's a problem.
"""
try:
r = self.session.get(url + 'save_grade')
r = self.session.get(self.url + 'save_grade')
except requests.exceptions.ConnectionError as err:
# reraise as promised GradingServiceError, but preserve stacktrace.
raise GradingServiceError, str(err), sys.exc_info()[2]
return r.text
_service = StaffGradingService(settings.STAFF_GRADING_BACKEND_URL)
#_service = StaffGradingService(settings.STAFF_GRADING_BACKEND_URL)
_service = MockStaffGradingService()
def _err_response(msg):
"""
......
......@@ -8,15 +8,24 @@ Notes for running by hand:
django-admin.py test --settings=lms.envs.test --pythonpath=. lms/djangoapps/instructor
"""
import courseware.tests.tests as ct
import json
from nose import SkipTest
from mock import patch, Mock
from override_settings import override_settings
from django.contrib.auth.models import \
Group # Need access to internal func to put users in the right group
# Need access to internal func to put users in the right group
from django.contrib.auth.models import Group
from django.core.urlresolvers import reverse
from django_comment_client.models import Role, FORUM_ROLE_ADMINISTRATOR, \
FORUM_ROLE_MODERATOR, FORUM_ROLE_COMMUNITY_TA, FORUM_ROLE_STUDENT
from django_comment_client.utils import has_forum_access
from instructor import staff_grading_service
from courseware.access import _course_staff_group_name
import courseware.tests.tests as ct
from xmodule.modulestore.django import modulestore
......@@ -79,7 +88,7 @@ class TestInstructorDashboardGradeDownloadCSV(ct.PageLoader):
'''
self.assertEqual(body, expected_body, msg)
FORUM_ROLES = [ FORUM_ROLE_ADMINISTRATOR, FORUM_ROLE_MODERATOR, FORUM_ROLE_COMMUNITY_TA ]
FORUM_ADMIN_ACTION_SUFFIX = { FORUM_ROLE_ADMINISTRATOR : 'admin', FORUM_ROLE_MODERATOR : 'moderator', FORUM_ROLE_COMMUNITY_TA : 'community TA'}
FORUM_ADMIN_USER = { FORUM_ROLE_ADMINISTRATOR : 'forumadmin', FORUM_ROLE_MODERATOR : 'forummoderator', FORUM_ROLE_COMMUNITY_TA : 'forummoderator'}
......@@ -91,6 +100,8 @@ def action_name(operation, rolename):
return '{0} forum {1}'.format(operation, FORUM_ADMIN_ACTION_SUFFIX[rolename])
_mock_service = staff_grading_service.MockStaffGradingService()
@override_settings(MODULESTORE=ct.TEST_DATA_XML_MODULESTORE)
class TestInstructorDashboardForumAdmin(ct.PageLoader):
'''
......@@ -101,8 +112,15 @@ class TestInstructorDashboardForumAdmin(ct.PageLoader):
xmodule.modulestore.django._MODULESTORES = {}
courses = modulestore().get_courses()
<<<<<<< HEAD
self.full = modulestore().get_course("edX/full/6.002_Spring_2012")
self.toy = modulestore().get_course("edX/toy/2012_Fall")
=======
self.course_id = "edX/toy/2012_Fall"
self.toy = modulestore().get_course(self.course_id)
>>>>>>> Refactor testing code, hook up frontend.
# Create two accounts
self.student = 'view@test.com'
......@@ -122,7 +140,7 @@ class TestInstructorDashboardForumAdmin(ct.PageLoader):
self.enroll(self.toy)
def initialize_roles(self, course_id):
self.admin_role = Role.objects.get_or_create(name=FORUM_ROLE_ADMINISTRATOR, course_id=course_id)[0]
self.moderator_role = Role.objects.get_or_create(name=FORUM_ROLE_MODERATOR, course_id=course_id)[0]
......@@ -220,7 +238,7 @@ class TestStaffGradingService(ct.PageLoader):
'''
def setUp(self):
xmodule.modulestore.django._MODULESTORES = {}
......@@ -240,7 +258,6 @@ class TestStaffGradingService(ct.PageLoader):
Make sure only staff have access.
"""
self.login(self.student, self.password)
self.enroll(self.toy)
# both get and post should return 404
for view_name in ('staff_grading_get_next', 'staff_grading_save_grade'):
......@@ -248,3 +265,32 @@ class TestStaffGradingService(ct.PageLoader):
self.check_for_get_code(404, url)
self.check_for_post_code(404, url)
<<<<<<< HEAD
=======
@patch.object(staff_grading_service, '_service', _mock_service)
def test_get_next(self):
self.login(self.instructor, self.password)
url = reverse('staff_grading_get_next', kwargs={'course_id': self.course_id})
r = self.check_for_get_code(200, url)
d = json.loads(r.content)
self.assertTrue(d['success'])
self.assertEquals(d['submission_id'], _mock_service.cnt)
@patch.object(staff_grading_service, '_service', _mock_service)
def test_save_grade(self):
self.login(self.instructor, self.password)
url = reverse('staff_grading_save_grade', kwargs={'course_id': self.course_id})
data = {'score': '12', 'feedback': 'great!', 'submission_id': '123'}
r = self.check_for_post_code(200, url, data)
d = json.loads(r.content)
self.assertTrue(d['success'], str(d))
self.assertEquals(d['submission_id'], _mock_service.cnt)
>>>>>>> Refactor testing code, hook up frontend.
......@@ -422,10 +422,15 @@ def staff_grading(request, course_id):
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, })
......
......@@ -24,6 +24,7 @@ class StaffGradingBackend
success: true
submission: 'submission! ' + @mock_cnt
rubric: 'A rubric! ' + @mock_cnt
submission_id: @mock_cnt
else if cmd == 'save_grade'
console.log("eval: #{data.score} pts, Feedback: #{data.feedback}")
......@@ -31,6 +32,7 @@ class StaffGradingBackend
success: true
submission: 'another submission! ' + @mock_cnt
rubric: 'A rubric!' + @mock_cnt
submission_id: @mock_cnt
else
response =
success: false
......@@ -74,6 +76,7 @@ class StaffGrading
# model state
@state = state_no_data
@submission_id = null
@submission = ''
@rubric = ''
@error_msg = ''
......@@ -110,7 +113,7 @@ class StaffGrading
if response.success
if response.submission
@data_loaded(response.submission, response.rubric)
@data_loaded(response.submission, response.rubric, response.submission_id)
else
@no_more(response.message)
else
......@@ -122,7 +125,10 @@ class StaffGrading
@backend.post('get_next', {}, @ajax_callback)
submit_and_get_next: () ->
data = {score: @score, feedback: @feedback_area.val()}
data =
score: @score
feedback: @feedback_area.val()
submission_id: @submission_id
@backend.post('save_grade', data, @ajax_callback)
......@@ -130,9 +136,10 @@ class StaffGrading
@error_msg = msg
@state = state_error
data_loaded: (submission, rubric) ->
data_loaded: (submission, rubric, submission_id) ->
@submission = submission
@rubric = rubric
@submission_id = submission_id
@feedback_area.val('')
@score = null
@state = state_grading
......@@ -140,6 +147,7 @@ class StaffGrading
no_more: (message) ->
@submission = null
@rubric = null
@submission_id = null
@message = message
@state = state_no_data
......@@ -196,7 +204,7 @@ class StaffGrading
# for now, just create an instance and load it...
mock_backend = true
mock_backend = false
ajax_url = $('.staff-grading').data('ajax_url')
backend = new StaffGradingBackend(ajax_url, mock_backend)
......
......@@ -15,7 +15,44 @@
</%block>
<section class="container">
<div class="grading-wrapper">
${view_html}
</div>
<div class="staff-grading" data-ajax_url="${ajax_url}">
<h1>Staff grading</h1>
<div class="error-container">
</div>
<div class="message-container">
</div>
<section class="submission-wrapper">
<h3>Submission</h3>
<div class="submission-container">
</div>
</section>
<section class="rubric-wrapper">
<h3>Rubric</h3>
<div class="rubric-container">
</div>
<div class="evaluation">
<textarea name="feedback" placeholder="Feedback for student..."
class="feedback-area" cols="70" rows="10"></textarea>
<p>
<label for="correct-radio">Correct</label>
<input type="radio" name="score-selection" id="correct-radio" value="1">
<label for="incorrect-radio">Incorrect</label>
<input type="radio" name="score-selection" id="incorrect-radio" value="0">
</p>
</div>
</section>
<div class="submission">
<input type="button" value="Submit" class="submit-button" name="show"/>
</div>
</div>
</section>
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