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
559bf887
Commit
559bf887
authored
Jan 03, 2013
by
David Ormsbee
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #1132 from MITx/vik/modify_open_ended
Vik/modify open ended
parents
ecd2304c
90d902d1
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
238 additions
and
13 deletions
+238
-13
common/lib/capa/capa/capa_problem.py
+18
-0
common/lib/capa/capa/inputtypes.py
+1
-1
common/lib/capa/capa/responsetypes.py
+100
-11
common/lib/capa/capa/templates/openendedinput.html
+25
-1
common/lib/xmodule/xmodule/capa_module.py
+15
-0
common/lib/xmodule/xmodule/css/capa/display.scss
+49
-0
common/lib/xmodule/xmodule/js/src/capa/display.coffee
+30
-0
No files found.
common/lib/capa/capa/capa_problem.py
View file @
559bf887
...
...
@@ -186,6 +186,24 @@ class LoncapaProblem(object):
maxscore
+=
responder
.
get_max_score
()
return
maxscore
def
message_post
(
self
,
event_info
):
"""
Handle an ajax post that contains feedback on feedback
Returns a boolean success variable
Note: This only allows for feedback to be posted back to the grading controller for the first
open ended response problem on each page. Multiple problems will cause some sync issues.
TODO: Handle multiple problems on one page sync issues.
"""
success
=
False
message
=
"Could not find a valid responder."
log
.
debug
(
"in lcp"
)
for
responder
in
self
.
responders
.
values
():
if
hasattr
(
responder
,
'handle_message_post'
):
success
,
message
=
responder
.
handle_message_post
(
event_info
)
if
success
:
break
return
success
,
message
def
get_score
(
self
):
"""
Compute score for this problem. The score is the number of points awarded.
...
...
common/lib/capa/capa/inputtypes.py
View file @
559bf887
...
...
@@ -748,7 +748,7 @@ class OpenEndedInput(InputTypeBase):
# pulled out for testing
submitted_msg
=
(
"Feedback not yet available. Reload to check again. "
"Once the problem is graded, this message will be "
"replaced with the grader's feedback"
)
"replaced with the grader's feedback
.
"
)
@classmethod
def
get_attributes
(
cls
):
...
...
common/lib/capa/capa/responsetypes.py
View file @
559bf887
...
...
@@ -1836,6 +1836,7 @@ class OpenEndedResponse(LoncapaResponse):
"""
DEFAULT_QUEUE
=
'open-ended'
DEFAULT_MESSAGE_QUEUE
=
'open-ended-message'
response_tag
=
'openendedresponse'
allowed_inputfields
=
[
'openendedinput'
]
max_inputfields
=
1
...
...
@@ -1847,12 +1848,17 @@ class OpenEndedResponse(LoncapaResponse):
xml
=
self
.
xml
self
.
url
=
xml
.
get
(
'url'
,
None
)
self
.
queue_name
=
xml
.
get
(
'queuename'
,
self
.
DEFAULT_QUEUE
)
self
.
message_queue_name
=
xml
.
get
(
'message-queuename'
,
self
.
DEFAULT_MESSAGE_QUEUE
)
# The openendedparam tag encapsulates all grader settings
oeparam
=
self
.
xml
.
find
(
'openendedparam'
)
prompt
=
self
.
xml
.
find
(
'prompt'
)
rubric
=
self
.
xml
.
find
(
'openendedrubric'
)
#This is needed to attach feedback to specific responses later
self
.
submission_id
=
None
self
.
grader_id
=
None
if
oeparam
is
None
:
raise
ValueError
(
"No oeparam found in problem xml."
)
if
prompt
is
None
:
...
...
@@ -1899,23 +1905,81 @@ class OpenEndedResponse(LoncapaResponse):
# response types)
except
TypeError
,
ValueError
:
log
.
exception
(
"Grader payload
%
r is not a json object!"
,
grader_payload
)
self
.
initial_display
=
find_with_default
(
oeparam
,
'initial_display'
,
''
)
self
.
answer
=
find_with_default
(
oeparam
,
'answer_display'
,
'No answer given.'
)
parsed_grader_payload
.
update
({
'location'
:
self
.
system
.
location
,
'course_id'
:
self
.
system
.
course_id
,
'prompt'
:
prompt_string
,
'rubric'
:
rubric_string
,
})
'initial_display'
:
self
.
initial_display
,
'answer'
:
self
.
answer
,
})
updated_grader_payload
=
json
.
dumps
(
parsed_grader_payload
)
self
.
payload
=
{
'grader_payload'
:
updated_grader_payload
}
self
.
initial_display
=
find_with_default
(
oeparam
,
'initial_display'
,
''
)
self
.
answer
=
find_with_default
(
oeparam
,
'answer_display'
,
'No answer given.'
)
try
:
self
.
max_score
=
int
(
find_with_default
(
oeparam
,
'max_score'
,
1
))
except
ValueError
:
self
.
max_score
=
1
def
handle_message_post
(
self
,
event_info
):
"""
Handles a student message post (a reaction to the grade they received from an open ended grader type)
Returns a boolean success/fail and an error message
"""
survey_responses
=
event_info
[
'survey_responses'
]
for
tag
in
[
'feedback'
,
'submission_id'
,
'grader_id'
,
'score'
]:
if
tag
not
in
survey_responses
:
return
False
,
"Could not find needed tag {0}"
.
format
(
tag
)
try
:
submission_id
=
int
(
survey_responses
[
'submission_id'
])
grader_id
=
int
(
survey_responses
[
'grader_id'
])
feedback
=
str
(
survey_responses
[
'feedback'
]
.
encode
(
'ascii'
,
'ignore'
))
score
=
int
(
survey_responses
[
'score'
])
except
:
error_message
=
(
"Could not parse submission id, grader id, "
"or feedback from message_post ajax call. Here is the message data: {0}"
.
format
(
survey_responses
))
log
.
exception
(
error_message
)
return
False
,
"There was an error saving your feedback. Please contact course staff."
qinterface
=
self
.
system
.
xqueue
[
'interface'
]
qtime
=
datetime
.
strftime
(
datetime
.
now
(),
xqueue_interface
.
dateformat
)
anonymous_student_id
=
self
.
system
.
anonymous_student_id
queuekey
=
xqueue_interface
.
make_hashkey
(
str
(
self
.
system
.
seed
)
+
qtime
+
anonymous_student_id
+
self
.
answer_id
)
xheader
=
xqueue_interface
.
make_xheader
(
lms_callback_url
=
self
.
system
.
xqueue
[
'callback_url'
],
lms_key
=
queuekey
,
queue_name
=
self
.
message_queue_name
)
student_info
=
{
'anonymous_student_id'
:
anonymous_student_id
,
'submission_time'
:
qtime
,
}
contents
=
{
'feedback'
:
feedback
,
'submission_id'
:
submission_id
,
'grader_id'
:
grader_id
,
'score'
:
score
,
'student_info'
:
json
.
dumps
(
student_info
),
}
(
error
,
msg
)
=
qinterface
.
send_to_queue
(
header
=
xheader
,
body
=
json
.
dumps
(
contents
))
#Convert error to a success value
success
=
True
if
error
:
success
=
False
return
success
,
"Successfully submitted your feedback."
def
get_score
(
self
,
student_answers
):
try
:
...
...
@@ -1956,7 +2020,7 @@ class OpenEndedResponse(LoncapaResponse):
contents
.
update
({
'student_info'
:
json
.
dumps
(
student_info
),
'student_response'
:
submission
,
'max_score'
:
self
.
max_score
'max_score'
:
self
.
max_score
,
})
# Submit request. When successful, 'msg' is the prior length of the queue
...
...
@@ -2056,18 +2120,36 @@ class OpenEndedResponse(LoncapaResponse):
"""
return
priorities
.
get
(
elt
[
0
],
default_priority
)
def
encode_values
(
feedback_type
,
value
):
feedback_type
=
str
(
feedback_type
)
.
encode
(
'ascii'
,
'ignore'
)
if
not
isinstance
(
value
,
basestring
):
value
=
str
(
value
)
value
=
value
.
encode
(
'ascii'
,
'ignore'
)
return
feedback_type
,
value
def
format_feedback
(
feedback_type
,
value
):
return
"""
feedback_type
,
value
=
encode_values
(
feedback_type
,
value
)
feedback
=
"""
<div class="{feedback_type}">
{value}
</div>
"""
.
format
(
feedback_type
=
feedback_type
,
value
=
value
)
return
feedback
def
format_feedback_hidden
(
feedback_type
,
value
):
feedback_type
,
value
=
encode_values
(
feedback_type
,
value
)
feedback
=
"""
<div class="{feedback_type}" style="display: none;">
{value}
</div>
"""
.
format
(
feedback_type
=
feedback_type
,
value
=
value
)
return
feedback
# TODO (vshnayder): design and document the details of this format so
# that we can do proper escaping here (e.g. are the graders allowed to
# include HTML?)
for
tag
in
[
'success'
,
'feedback'
]:
for
tag
in
[
'success'
,
'feedback'
,
'submission_id'
,
'grader_id'
]:
if
tag
not
in
response_items
:
return
format_feedback
(
'errors'
,
'Error getting feedback'
)
...
...
@@ -2083,10 +2165,15 @@ class OpenEndedResponse(LoncapaResponse):
return
format_feedback
(
'errors'
,
'No feedback available'
)
feedback_lst
=
sorted
(
feedback
.
items
(),
key
=
get_priority
)
return
u"
\n
"
.
join
(
format_feedback
(
k
,
v
)
for
k
,
v
in
feedback_lst
)
feedback_list_part1
=
u"
\n
"
.
join
(
format_feedback
(
k
,
v
)
for
k
,
v
in
feedback_lst
)
else
:
return
format_feedback
(
'errors'
,
response_items
[
'feedback'
])
feedback_list_part1
=
format_feedback
(
'errors'
,
response_items
[
'feedback'
])
feedback_list_part2
=
(
u"
\n
"
.
join
([
format_feedback_hidden
(
feedback_type
,
value
)
for
feedback_type
,
value
in
response_items
.
items
()
if
feedback_type
in
[
'submission_id'
,
'grader_id'
]]))
return
u"
\n
"
.
join
([
feedback_list_part1
,
feedback_list_part2
])
def
_format_feedback
(
self
,
response_items
):
"""
...
...
@@ -2104,7 +2191,7 @@ class OpenEndedResponse(LoncapaResponse):
feedback_template
=
self
.
system
.
render_template
(
"open_ended_feedback.html"
,
{
'grader_type'
:
response_items
[
'grader_type'
],
'score'
:
response_items
[
'score'
]
,
'score'
:
"{0} / {1}"
.
format
(
response_items
[
'score'
],
self
.
max_score
)
,
'feedback'
:
feedback
,
})
...
...
@@ -2138,17 +2225,19 @@ class OpenEndedResponse(LoncapaResponse):
" Received score_result = {0}"
.
format
(
score_result
))
return
fail
for
tag
in
[
'score'
,
'feedback'
,
'grader_type'
,
'success'
]:
for
tag
in
[
'score'
,
'feedback'
,
'grader_type'
,
'success'
,
'grader_id'
,
'submission_id'
]:
if
tag
not
in
score_result
:
log
.
error
(
"External grader message is missing required tag: {0}"
.
format
(
tag
))
return
fail
feedback
=
self
.
_format_feedback
(
score_result
)
self
.
submission_id
=
score_result
[
'submission_id'
]
self
.
grader_id
=
score_result
[
'grader_id'
]
# HACK: for now, just assume it's correct if you got more than 2/3.
# Also assumes that score_result['score'] is an integer.
score_ratio
=
int
(
score_result
[
'score'
])
/
self
.
max_score
score_ratio
=
int
(
score_result
[
'score'
])
/
float
(
self
.
max_score
)
correct
=
(
score_ratio
>=
0.66
)
#Currently ignore msg and only return feedback (which takes the place of msg)
...
...
common/lib/capa/capa/templates/openendedinput.html
View file @
559bf887
...
...
@@ -27,6 +27,30 @@
<input
name=
"reload"
class=
"reload"
type=
"button"
value=
"Recheck for Feedback"
onclick=
"document.location.reload(true);"
/>
% endif
<div
class=
"external-grader-message"
>
${msg|n}
${msg|n}
% if status in ['correct','incorrect']:
<div
class=
"collapsible evaluation-response"
>
<header>
<a
href=
"#"
>
Respond to Feedback
</a>
</header>
<section
id=
"evaluation_${id}"
class=
"evaluation"
>
<p>
How accurate do you find this feedback?
</p>
<div
class=
"evaluation-scoring"
>
<ul
class=
"scoring-list"
>
<li><input
type=
"radio"
name=
"evaluation-score"
id=
"evaluation-score-5"
value=
"5"
/>
<label
for=
"evaluation-score-5"
>
Correct
</label></li>
<li><input
type=
"radio"
name=
"evaluation-score"
id=
"evaluation-score-4"
value=
"4"
/>
<label
for=
"evaluation-score-4"
>
Partially Correct
</label></li>
<li><input
type=
"radio"
name=
"evaluation-score"
id=
"evaluation-score-3"
value=
"3"
/>
<label
for=
"evaluation-score-3"
>
No Opinion
</label></li>
<li><input
type=
"radio"
name=
"evaluation-score"
id=
"evaluation-score-2"
value=
"2"
/>
<label
for=
"evaluation-score-2"
>
Partially Incorrect
</label></li>
<li><input
type=
"radio"
name=
"evaluation-score"
id=
"evaluation-score-1"
value=
"1"
/>
<label
for=
"evaluation-score-1"
>
Incorrect
</label></li>
</ul>
</div>
<p>
Additional comments:
</p>
<textarea
rows=
"${rows}"
cols=
"${cols}"
name=
"feedback_${id}"
class=
"feedback-on-feedback"
id=
"feedback_${id}"
></textarea>
<div
class=
"submit-message-container"
>
<input
name=
"submit-message"
class=
"submit-message"
type=
"button"
value=
"Submit your message"
/>
</div>
</section>
</div>
% endif
</div>
</section>
common/lib/xmodule/xmodule/capa_module.py
View file @
559bf887
...
...
@@ -380,6 +380,7 @@ class CapaModule(XModule):
'problem_save'
:
self
.
save_problem
,
'problem_show'
:
self
.
get_answer
,
'score_update'
:
self
.
update_score
,
'message_post'
:
self
.
message_post
,
}
if
dispatch
not
in
handlers
:
...
...
@@ -394,6 +395,20 @@ class CapaModule(XModule):
})
return
json
.
dumps
(
d
,
cls
=
ComplexEncoder
)
def
message_post
(
self
,
get
):
"""
Posts a message from a form to an appropriate location
"""
event_info
=
dict
()
event_info
[
'state'
]
=
self
.
lcp
.
get_state
()
event_info
[
'problem_id'
]
=
self
.
location
.
url
()
event_info
[
'student_id'
]
=
self
.
system
.
anonymous_student_id
event_info
[
'survey_responses'
]
=
get
success
,
message
=
self
.
lcp
.
message_post
(
event_info
)
return
{
'success'
:
success
,
'message'
:
message
}
def
closed
(
self
):
''' Is the student still allowed to submit answers? '''
if
self
.
attempts
==
self
.
max_attempts
:
...
...
common/lib/xmodule/xmodule/css/capa/display.scss
View file @
559bf887
...
...
@@ -297,6 +297,51 @@ section.problem {
float
:
left
;
}
}
}
.evaluation
{
p
{
margin-bottom
:
4px
;
}
}
.feedback-on-feedback
{
height
:
100px
;
margin-right
:
20px
;
}
.evaluation-response
{
header
{
text-align
:
right
;
a
{
font-size
:
.85em
;
}
}
}
.evaluation-scoring
{
.scoring-list
{
list-style-type
:
none
;
margin-left
:
3px
;
li
{
&
:first-child
{
margin-left
:
0px
;
}
display
:inline
;
margin-left
:
50px
;
label
{
font-size
:
.9em
;
}
}
}
}
.submit-message-container
{
margin
:
10px
0px
;
}
}
...
...
@@ -634,6 +679,10 @@ section.problem {
color
:
#2C2C2C
;
font-family
:
monospace
;
font-size
:
1em
;
padding-top
:
10px
;
header
{
font-size
:
1
.4em
;
}
.shortform
{
font-weight
:
bold
;
...
...
common/lib/xmodule/xmodule/js/src/capa/display.coffee
View file @
559bf887
...
...
@@ -25,6 +25,7 @@ class @Problem
@
$
(
'section.action input.reset'
).
click
@
reset
@
$
(
'section.action input.show'
).
click
@
show
@
$
(
'section.action input.save'
).
click
@
save
@
$
(
'section.evaluation input.submit-message'
).
click
@
message_post
# Collapsibles
Collapsible
.
setCollapsibles
(
@
el
)
...
...
@@ -197,6 +198,35 @@ class @Problem
else
@
gentle_alert
response
.
success
message_post
:
=>
Logger
.
log
'message_post'
,
@
answers
fd
=
new
FormData
()
feedback
=
@
$
(
'section.evaluation textarea.feedback-on-feedback'
)[
0
].
value
submission_id
=
$
(
'div.external-grader-message div.submission_id'
)[
0
].
innerHTML
grader_id
=
$
(
'div.external-grader-message div.grader_id'
)[
0
].
innerHTML
score
=
$
(
".evaluation-scoring input:radio[name='evaluation-score']:checked"
).
val
()
fd
.
append
(
'feedback'
,
feedback
)
fd
.
append
(
'submission_id'
,
submission_id
)
fd
.
append
(
'grader_id'
,
grader_id
)
if
(
!
score
)
@
gentle_alert
"You need to pick a rating before you can submit."
return
else
fd
.
append
(
'score'
,
score
)
settings
=
type
:
"POST"
data
:
fd
processData
:
false
contentType
:
false
success
:
(
response
)
=>
@
gentle_alert
response
.
message
@
$
(
'section.evaluation'
).
slideToggle
()
$
.
ajaxWithPrefix
(
"
#{
@
url
}
/message_post"
,
settings
)
reset
:
=>
Logger
.
log
'problem_reset'
,
@
answers
$
.
postWithPrefix
"
#{
@
url
}
/problem_reset"
,
id
:
@
id
,
(
response
)
=>
...
...
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