Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
d0e2b85e
Commit
d0e2b85e
authored
Nov 19, 2012
by
Victor Shnayder
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Refactor testing code, hook up frontend.
- now getting requests from js to server and back, with mocked service.
parent
ff119265
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
179 additions
and
60 deletions
+179
-60
lms/djangoapps/courseware/tests/tests.py
+45
-41
lms/djangoapps/instructor/staff_grading_service.py
+25
-6
lms/djangoapps/instructor/tests.py
+52
-6
lms/djangoapps/instructor/views.py
+5
-0
lms/static/coffee/src/staff_grading/staff_grading.coffee
+12
-4
lms/templates/instructor/staff_grading.html
+40
-3
No files found.
lms/djangoapps/courseware/tests/tests.py
View file @
d0e2b85e
...
...
@@ -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%
...
...
lms/djangoapps/instructor/staff_grading_service.py
View file @
d0e2b85e
...
...
@@ -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
):
"""
...
...
lms/djangoapps/instructor/tests.py
View file @
d0e2b85e
...
...
@@ -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
.
lms/djangoapps/instructor/views.py
View file @
d0e2b85e
...
...
@@ -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
,
})
...
...
lms/static/coffee/src/staff_grading/staff_grading.coffee
View file @
d0e2b85e
...
...
@@ -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
=
tru
e
mock_backend
=
fals
e
ajax_url
=
$
(
'.staff-grading'
).
data
(
'ajax_url'
)
backend
=
new
StaffGradingBackend
(
ajax_url
,
mock_backend
)
...
...
lms/templates/instructor/staff_grading.html
View file @
d0e2b85e
...
...
@@ -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>
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment