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
7c0bc4c7
Commit
7c0bc4c7
authored
Jul 16, 2012
by
Calen Pennington
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #180 from MITx/kimth/lms-coderesponse
CodeResponse for external/queued grading of student code
parents
82c11ea0
80828384
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
189 additions
and
3 deletions
+189
-3
common/lib/capa/capa/capa_problem.py
+9
-0
common/lib/capa/capa/responsetypes.py
+120
-1
common/lib/xmodule/xmodule/capa_module.py
+7
-0
lms/djangoapps/courseware/module_render.py
+52
-2
lms/urls.py
+1
-0
No files found.
common/lib/capa/capa/capa_problem.py
View file @
7c0bc4c7
...
...
@@ -179,6 +179,15 @@ class LoncapaProblem(object):
return
{
'score'
:
correct
,
'total'
:
self
.
get_max_score
()}
def
update_score
(
self
,
score_msg
):
newcmap
=
CorrectMap
()
for
responder
in
self
.
responders
.
values
():
if
hasattr
(
responder
,
'update_score'
):
# Is this the best way to implement 'update_score' for CodeResponse?
results
=
responder
.
update_score
(
score_msg
)
newcmap
.
update
(
results
)
self
.
correct_map
=
newcmap
return
newcmap
def
grade_answers
(
self
,
answers
):
'''
Grade student responses. Called by capa_module.check_problem.
...
...
common/lib/capa/capa/responsetypes.py
View file @
7c0bc4c7
...
...
@@ -18,6 +18,7 @@ import re
import
requests
import
traceback
import
abc
import
time
# specific library imports
from
calc
import
evaluator
,
UndefinedVariable
...
...
@@ -693,6 +694,124 @@ class SymbolicResponse(CustomResponse):
#-----------------------------------------------------------------------------
class
CodeResponse
(
LoncapaResponse
):
'''
Grade student code using an external server
'''
response_tag
=
'coderesponse'
allowed_inputfields
=
[
'textline'
,
'textbox'
]
def
setup_response
(
self
):
xml
=
self
.
xml
self
.
url
=
xml
.
get
(
'url'
)
or
"http://ec2-50-16-59-149.compute-1.amazonaws.com/xqueue/submit/"
# FIXME -- hardcoded url
answer
=
xml
.
find
(
'answer'
)
if
answer
is
not
None
:
answer_src
=
answer
.
get
(
'src'
)
if
answer_src
is
not
None
:
self
.
code
=
self
.
system
.
filesystem
.
open
(
'src/'
+
answer_src
)
.
read
()
else
:
self
.
code
=
answer
.
text
else
:
# no <answer> stanza; get code from <script>
self
.
code
=
self
.
context
[
'script_code'
]
if
not
self
.
code
:
msg
=
'
%
s: Missing answer script code for externalresponse'
%
unicode
(
self
)
msg
+=
"
\n
See XML source line
%
s"
%
getattr
(
self
.
xml
,
'sourceline'
,
'<unavailable>'
)
raise
LoncapaProblemError
(
msg
)
self
.
tests
=
xml
.
get
(
'tests'
)
def
get_score
(
self
,
student_answers
):
idset
=
sorted
(
self
.
answer_ids
)
try
:
submission
=
[
student_answers
[
k
]
for
k
in
idset
]
except
Exception
as
err
:
log
.
error
(
'Error in CodeResponse
%
s: cannot get student answer for
%
s; student_answers=
%
s'
%
(
err
,
self
.
answer_ids
,
student_answers
))
raise
Exception
(
err
)
self
.
context
.
update
({
'submission'
:
submission
})
extra_payload
=
{
'edX_student_response'
:
json
.
dumps
(
submission
)}
# Should do something -- like update the problem state -- based on the queue response
r
=
self
.
_send_to_queue
(
extra_payload
)
return
CorrectMap
()
def
update_score
(
self
,
score_msg
):
# Parse 'score_msg' as XML
try
:
rxml
=
etree
.
fromstring
(
score_msg
)
except
Exception
as
err
:
msg
=
'Error in CodeResponse
%
s: cannot parse response from xworker r.text=
%
s'
%
(
err
,
score_msg
)
raise
Exception
(
err
)
# The following process is lifted directly from ExternalResponse
idset
=
sorted
(
self
.
answer_ids
)
cmap
=
CorrectMap
()
ad
=
rxml
.
find
(
'awarddetail'
)
.
text
admap
=
{
'EXACT_ANS'
:
'correct'
,
# TODO: handle other loncapa responses
'WRONG_FORMAT'
:
'incorrect'
,
}
self
.
context
[
'correct'
]
=
[
'correct'
]
if
ad
in
admap
:
self
.
context
[
'correct'
][
0
]
=
admap
[
ad
]
# create CorrectMap
for
key
in
idset
:
idx
=
idset
.
index
(
key
)
msg
=
rxml
.
find
(
'message'
)
.
text
.
replace
(
' '
,
' '
)
if
idx
==
0
else
None
cmap
.
set
(
key
,
self
.
context
[
'correct'
][
idx
],
msg
=
msg
)
return
cmap
# CodeResponse differentiates from ExternalResponse in the behavior of 'get_answers'. CodeResponse.get_answers
# does NOT require a queue submission, and the answer is computed (extracted from problem XML) locally.
def
get_answers
(
self
):
# Extract the CodeResponse answer from XML
penv
=
{}
penv
[
'__builtins__'
]
=
globals
()[
'__builtins__'
]
try
:
exec
(
self
.
code
,
penv
,
penv
)
except
Exception
as
err
:
log
.
error
(
'Error in CodeResponse
%
s: Error in problem reference code'
%
err
)
raise
Exception
(
err
)
try
:
ans
=
penv
[
'answer'
]
except
Exception
as
err
:
log
.
error
(
'Error in CodeResponse
%
s: Problem reference code does not define answer in <answer>...</answer>'
%
err
)
raise
Exception
(
err
)
anshtml
=
'<font color="blue"><span class="code-answer"><br/><pre>
%
s</pre><br/></span></font>'
%
ans
return
dict
(
zip
(
self
.
answer_ids
,[
anshtml
]))
# CodeResponse._send_to_queue implements the same interface as defined for ExternalResponse's 'get_score'
def
_send_to_queue
(
self
,
extra_payload
):
# Prepare payload
xmlstr
=
etree
.
tostring
(
self
.
xml
,
pretty_print
=
True
)
header
=
{
'return_url'
:
self
.
system
.
xqueue_callback_url
}
header
.
update
({
'timestamp'
:
time
.
time
()})
payload
=
{
'xqueue_header'
:
json
.
dumps
(
header
),
# 'xqueue_header' should eventually be derived from xqueue.queue_common.HEADER_TAG or something similar
'xml'
:
xmlstr
,
'edX_cmd'
:
'get_score'
,
'edX_tests'
:
self
.
tests
,
'processor'
:
self
.
code
,
}
payload
.
update
(
extra_payload
)
# Contact queue server
try
:
r
=
requests
.
post
(
self
.
url
,
data
=
payload
)
except
Exception
as
err
:
msg
=
"Error in CodeResponse
%
s: cannot connect to queue server url=
%
s"
%
(
err
,
self
.
url
)
log
.
error
(
msg
)
raise
Exception
(
msg
)
return
r
#-----------------------------------------------------------------------------
class
ExternalResponse
(
LoncapaResponse
):
'''
Grade the students input using an external server.
...
...
@@ -1072,5 +1191,5 @@ class ImageResponse(LoncapaResponse):
# TEMPORARY: List of all response subclasses
# FIXME: To be replaced by auto-registration
__all__
=
[
NumericalResponse
,
FormulaResponse
,
CustomResponse
,
SchematicResponse
,
MultipleChoiceResponse
,
TrueFalseResponse
,
ExternalResponse
,
ImageResponse
,
OptionResponse
,
SymbolicResponse
,
StringResponse
]
__all__
=
[
CodeResponse
,
NumericalResponse
,
FormulaResponse
,
CustomResponse
,
SchematicResponse
,
MultipleChoiceResponse
,
TrueFalseResponse
,
ExternalResponse
,
ImageResponse
,
OptionResponse
,
SymbolicResponse
,
StringResponse
]
common/lib/xmodule/xmodule/capa_module.py
View file @
7c0bc4c7
...
...
@@ -275,6 +275,7 @@ class CapaModule(XModule):
'problem_reset'
:
self
.
reset_problem
,
'problem_save'
:
self
.
save_problem
,
'problem_show'
:
self
.
get_answer
,
'score_update'
:
self
.
update_score
,
}
if
dispatch
not
in
handlers
:
...
...
@@ -321,6 +322,12 @@ class CapaModule(XModule):
#TODO: Not 404
raise
self
.
system
.
exception404
def
update_score
(
self
,
get
):
score_msg
=
get
[
'response'
]
self
.
lcp
.
update_score
(
score_msg
)
return
dict
()
# No AJAX return is needed
def
get_answer
(
self
,
get
):
'''
For the "show answer" button.
...
...
lms/djangoapps/courseware/module_render.py
View file @
7c0bc4c7
...
...
@@ -28,7 +28,7 @@ class I4xSystem(object):
'''
def
__init__
(
self
,
ajax_url
,
track_function
,
get_module
,
render_template
,
replace_urls
,
user
=
None
,
filestore
=
None
):
user
=
None
,
filestore
=
None
,
xqueue_callback_url
=
None
):
'''
Create a closure around the system environment.
...
...
@@ -48,6 +48,7 @@ class I4xSystem(object):
that capa_module can use to fix up the static urls in ajax results.
'''
self
.
ajax_url
=
ajax_url
self
.
xqueue_callback_url
=
xqueue_callback_url
self
.
track_function
=
track_function
self
.
filestore
=
filestore
self
.
get_module
=
get_module
...
...
@@ -207,6 +208,7 @@ def get_module(user, request, location, student_module_cache, position=None):
# Setup system context for module instance
ajax_url
=
settings
.
MITX_ROOT_URL
+
'/modx/'
+
descriptor
.
location
.
url
()
+
'/'
xqueue_callback_url
=
settings
.
MITX_ROOT_URL
+
'/xqueue/'
+
user
.
username
+
'/'
+
descriptor
.
location
.
url
()
+
'/'
def
_get_module
(
location
):
(
module
,
_
,
_
,
_
)
=
get_module
(
user
,
request
,
location
,
student_module_cache
,
position
)
...
...
@@ -218,6 +220,7 @@ def get_module(user, request, location, student_module_cache, position=None):
system
=
I4xSystem
(
track_function
=
make_track_function
(
request
),
render_template
=
render_to_string
,
ajax_url
=
ajax_url
,
xqueue_callback_url
=
xqueue_callback_url
,
# TODO (cpennington): Figure out how to share info between systems
filestore
=
descriptor
.
system
.
resources_fs
,
get_module
=
_get_module
,
...
...
@@ -321,6 +324,53 @@ def add_histogram(module):
module
.
get_html
=
get_html
return
module
# THK: TEMPORARY BYPASS OF AUTH!
from
django.views.decorators.csrf
import
csrf_exempt
from
django.contrib.auth.models
import
User
@csrf_exempt
def
xqueue_callback
(
request
,
username
,
id
,
dispatch
):
# Parse xqueue response
get
=
request
.
POST
.
copy
()
try
:
header
=
json
.
loads
(
get
.
pop
(
'xqueue_header'
)[
0
])
# 'dict'
except
Exception
as
err
:
msg
=
"Error in xqueue_callback
%
s: Invalid return format"
%
err
raise
Exception
(
msg
)
# Should proceed only when the request timestamp is more recent than problem timestamp
timestamp
=
header
[
'timestamp'
]
# Retrieve target StudentModule
user
=
User
.
objects
.
get
(
username
=
username
)
student_module_cache
=
StudentModuleCache
(
user
,
modulestore
()
.
get_item
(
id
))
instance
,
instance_module
,
shared_module
,
module_type
=
get_module
(
request
.
user
,
request
,
id
,
student_module_cache
)
if
instance_module
is
None
:
log
.
debug
(
"Couldn't find module '
%
s' for user '
%
s'"
,
id
,
request
.
user
)
raise
Http404
oldgrade
=
instance_module
.
grade
old_instance_state
=
instance_module
.
state
# We go through the "AJAX" path
# So far, the only dispatch from xqueue will be 'score_update'
try
:
ajax_return
=
instance
.
handle_ajax
(
dispatch
,
get
)
# Can ignore the "ajax" return in 'xqueue_callback'
except
:
log
.
exception
(
"error processing ajax call"
)
raise
# Save state back to database
instance_module
.
state
=
instance
.
get_instance_state
()
if
instance
.
get_score
():
instance_module
.
grade
=
instance
.
get_score
()[
'score'
]
if
instance_module
.
grade
!=
oldgrade
or
instance_module
.
state
!=
old_instance_state
:
instance_module
.
save
()
return
HttpResponse
(
""
)
def
modx_dispatch
(
request
,
dispatch
=
None
,
id
=
None
):
''' Generic view for extensions. This is where AJAX calls go.
...
...
@@ -339,7 +389,7 @@ def modx_dispatch(request, dispatch=None, id=None):
student_module_cache
=
StudentModuleCache
(
request
.
user
,
modulestore
()
.
get_item
(
id
))
instance
,
instance_module
,
shared_module
,
module_type
=
get_module
(
request
.
user
,
request
,
id
,
student_module_cache
)
if
instance_module
is
None
:
log
.
debug
(
"Couldn't find module '
%
s' for user '
%
s'"
,
id
,
request
.
user
)
...
...
lms/urls.py
View file @
7c0bc4c7
...
...
@@ -58,6 +58,7 @@ if settings.COURSEWARE_ENABLED:
url
(
r'^masquerade/'
,
include
(
'masquerade.urls'
)),
url
(
r'^jumpto/(?P<probname>[^/]+)/$'
,
'courseware.views.jump_to'
),
url
(
r'^modx/(?P<id>.*?)/(?P<dispatch>[^/]*)$'
,
'courseware.module_render.modx_dispatch'
),
#reset_problem'),
url
(
r'^xqueue/(?P<username>[^/]*)/(?P<id>.*?)/(?P<dispatch>[^/]*)$'
,
'courseware.module_render.xqueue_callback'
),
url
(
r'^change_setting$'
,
'student.views.change_setting'
),
url
(
r'^s/(?P<template>[^/]*)$'
,
'static_template_view.views.auth_index'
),
url
(
r'^book/(?P<page>[^/]*)$'
,
'staticbook.views.index'
),
...
...
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