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
b88c6b8d
Commit
b88c6b8d
authored
Jul 11, 2013
by
Felix Sun
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Hinter now works with formula responses. Tests broken.
parent
c34a81a8
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
82 additions
and
22 deletions
+82
-22
common/lib/capa/capa/responsetypes.py
+1
-1
common/lib/xmodule/xmodule/crowdsource_hinter.py
+81
-21
No files found.
common/lib/capa/capa/responsetypes.py
View file @
b88c6b8d
...
@@ -1824,7 +1824,7 @@ class FormulaResponse(LoncapaResponse):
...
@@ -1824,7 +1824,7 @@ class FormulaResponse(LoncapaResponse):
log
.
debug
(
'formularesponse: error
%
s in formula'
,
err
)
log
.
debug
(
'formularesponse: error
%
s in formula'
,
err
)
raise
StudentInputError
(
"Invalid input: Could not parse '
%
s' as a formula"
%
raise
StudentInputError
(
"Invalid input: Could not parse '
%
s' as a formula"
%
cgi
.
escape
(
answer
))
cgi
.
escape
(
answer
))
return
tuple
(
out
)
return
out
def
randomize_variables
(
self
,
samples
):
def
randomize_variables
(
self
,
samples
):
"""
"""
...
...
common/lib/xmodule/xmodule/crowdsource_hinter.py
View file @
b88c6b8d
...
@@ -16,6 +16,8 @@ from xmodule.x_module import XModule
...
@@ -16,6 +16,8 @@ from xmodule.x_module import XModule
from
xmodule.xml_module
import
XmlDescriptor
from
xmodule.xml_module
import
XmlDescriptor
from
xblock.core
import
Scope
,
String
,
Integer
,
Boolean
,
Dict
,
List
from
xblock.core
import
Scope
,
String
,
Integer
,
Boolean
,
Dict
,
List
from
capa.responsetypes
import
FormulaResponse
,
StudentInputError
from
django.utils.html
import
escape
from
django.utils.html
import
escape
log
=
logging
.
getLogger
(
__name__
)
log
=
logging
.
getLogger
(
__name__
)
...
@@ -37,6 +39,14 @@ class CrowdsourceHinterFields(object):
...
@@ -37,6 +39,14 @@ class CrowdsourceHinterFields(object):
mod_queue
=
Dict
(
help
=
'A dictionary containing hints still awaiting approval'
,
scope
=
Scope
.
content
,
mod_queue
=
Dict
(
help
=
'A dictionary containing hints still awaiting approval'
,
scope
=
Scope
.
content
,
default
=
{})
default
=
{})
hint_pk
=
Integer
(
help
=
'Used to index hints.'
,
scope
=
Scope
.
content
,
default
=
0
)
hint_pk
=
Integer
(
help
=
'Used to index hints.'
,
scope
=
Scope
.
content
,
default
=
0
)
# signature_to_ans maps an answer signature to an answer string that shows that answer in a
# human-readable form.
signature_to_ans
=
Dict
(
help
=
'Maps a signature to a representative formula.'
,
scope
=
Scope
.
content
,
default
=
{})
# A list of dictionaries, each of which represents an n-dimenstional point that we plug into
# formulas. Each dictionary maps variables to values, eg {'x': 5.1}.
formula_test_values
=
List
(
help
=
'The values that we plug into formula responses'
,
scope
=
Scope
.
content
,
default
=
[])
# A list of previous answers this student made to this problem.
# A list of previous answers this student made to this problem.
# Of the form [answer, [hint_pk_1, hint_pk_2, hint_pk_3]] for each problem. hint_pk's are
# Of the form [answer, [hint_pk_1, hint_pk_2, hint_pk_3]] for each problem. hint_pk's are
# None if the hint was not given.
# None if the hint was not given.
...
@@ -68,6 +78,15 @@ class CrowdsourceHinterModule(CrowdsourceHinterFields, XModule):
...
@@ -68,6 +78,15 @@ class CrowdsourceHinterModule(CrowdsourceHinterFields, XModule):
def
__init__
(
self
,
*
args
,
**
kwargs
):
def
__init__
(
self
,
*
args
,
**
kwargs
):
XModule
.
__init__
(
self
,
*
args
,
**
kwargs
)
XModule
.
__init__
(
self
,
*
args
,
**
kwargs
)
# We need to know whether we are working with a FormulaResponse problem.
self
.
is_formula
=
(
type
(
self
.
get_display_items
()[
0
]
.
lcp
.
responders
.
values
()[
0
])
==
FormulaResponse
)
if
self
.
is_formula
:
self
.
answer_to_str
=
self
.
formula_answer_to_str
self
.
answer_signature
=
self
.
formula_answer_signature
else
:
self
.
answer_to_str
=
self
.
numerical_answer_to_str
# Right now, numerical problems don't need special answer signature treatment.
self
.
answer_signature
=
lambda
x
:
x
def
get_html
(
self
):
def
get_html
(
self
):
"""
"""
...
@@ -98,15 +117,45 @@ class CrowdsourceHinterModule(CrowdsourceHinterFields, XModule):
...
@@ -98,15 +117,45 @@ class CrowdsourceHinterModule(CrowdsourceHinterFields, XModule):
return
out
return
out
def
capa
_answer_to_str
(
self
,
answer
):
def
numerical
_answer_to_str
(
self
,
answer
):
"""
"""
Converts capa answer format to a string representation
Converts capa
numerical
answer format to a string representation
of the answer.
of the answer.
-Lon-capa dependent.
-Lon-capa dependent.
-Assumes that the problem only has one part.
-Assumes that the problem only has one part.
"""
"""
return
str
(
float
(
answer
.
values
()[
0
]))
return
str
(
float
(
answer
.
values
()[
0
]))
def
formula_answer_to_str
(
self
,
answer
):
"""
Converts capa formula answer into a string.
-Lon-capa dependent.
-Assumes that the problem only has one part.
"""
return
str
(
answer
.
values
()[
0
])
def
formula_answer_signature
(
self
,
answer
):
"""
Converts a capa answer string (output of formula_answer_to_str)
to a string unique to each formula equality class.
So, x^2 and x*x would have the same signature, which would differ
from the signature of 2*x^2.
"""
responder
=
self
.
get_display_items
()[
0
]
.
lcp
.
responders
.
values
()[
0
]
if
self
.
formula_test_values
==
[]:
# Make a set of test values, and save them.
self
.
formula_test_values
=
responder
.
randomize_variables
(
responder
.
samples
)
try
:
# TODO, maybe: add some rounding to signature generation, so that floating point
# errors don't make a difference.
out
=
str
(
responder
.
hash_answers
(
answer
,
self
.
formula_test_values
))
except
StudentInputError
:
# I'm not sure what's the best thing to do here.
# I'll return the empty string, for now.
# That way, all invalid hints are clustered together.
return
''
return
out
def
handle_ajax
(
self
,
dispatch
,
data
):
def
handle_ajax
(
self
,
dispatch
,
data
):
"""
"""
This is the landing method for AJAX calls.
This is the landing method for AJAX calls.
...
@@ -134,44 +183,46 @@ class CrowdsourceHinterModule(CrowdsourceHinterFields, XModule):
...
@@ -134,44 +183,46 @@ class CrowdsourceHinterModule(CrowdsourceHinterFields, XModule):
Called by hinter javascript after a problem is graded as incorrect.
Called by hinter javascript after a problem is graded as incorrect.
Args:
Args:
`data` -- must be interpretable by
capa_
answer_to_str.
`data` -- must be interpretable by answer_to_str.
Output keys:
Output keys:
- 'best_hint' is the hint text with the most votes.
- 'best_hint' is the hint text with the most votes.
- 'rand_hint_1' and 'rand_hint_2' are two random hints to the answer in `data`.
- 'rand_hint_1' and 'rand_hint_2' are two random hints to the answer in `data`.
- 'answer' is the parsed answer that was submitted.
- 'answer' is the parsed answer that was submitted.
"""
"""
try
:
try
:
answer
=
self
.
capa_
answer_to_str
(
data
)
answer
=
self
.
answer_to_str
(
data
)
except
ValueError
:
except
ValueError
:
# Sometimes, we get an answer that's just not parsable. Do nothing.
# Sometimes, we get an answer that's just not parsable. Do nothing.
log
.
exception
(
'Answer not parsable: '
+
str
(
data
))
log
.
exception
(
'Answer not parsable: '
+
str
(
data
))
return
return
# Make a signature of the answer, for formula responses.
signature
=
self
.
answer_signature
(
answer
)
# Look for a hint to give.
# Look for a hint to give.
# Make a local copy of self.hints - this means we only need to do one json unpacking.
# Make a local copy of self.hints - this means we only need to do one json unpacking.
# (This is because xblocks storage makes the following command a deep copy.)
# (This is because xblocks storage makes the following command a deep copy.)
local_hints
=
self
.
hints
local_hints
=
self
.
hints
if
(
answer
not
in
local_hints
)
or
(
len
(
local_hints
[
answer
])
==
0
):
if
(
signature
not
in
local_hints
)
or
(
len
(
local_hints
[
signature
])
==
0
):
# No hints to give. Return.
# No hints to give. Return.
self
.
previous_answers
+=
[[
answer
,
[
None
,
None
,
None
]]]
self
.
previous_answers
+=
[[
answer
,
[
None
,
None
,
None
]]]
return
return
# Get the top hint, plus two random hints.
# Get the top hint, plus two random hints.
n_hints
=
len
(
local_hints
[
answer
])
n_hints
=
len
(
local_hints
[
signature
])
best_hint_index
=
max
(
local_hints
[
answer
],
key
=
lambda
key
:
local_hints
[
answer
][
key
][
1
])
best_hint_index
=
max
(
local_hints
[
signature
],
key
=
lambda
key
:
local_hints
[
signature
][
key
][
1
])
best_hint
=
local_hints
[
answer
][
best_hint_index
][
0
]
best_hint
=
local_hints
[
signature
][
best_hint_index
][
0
]
if
len
(
local_hints
[
answer
])
==
1
:
if
len
(
local_hints
[
signature
])
==
1
:
rand_hint_1
=
''
rand_hint_1
=
''
rand_hint_2
=
''
rand_hint_2
=
''
self
.
previous_answers
+=
[[
answer
,
[
best_hint_index
,
None
,
None
]]]
self
.
previous_answers
+=
[[
answer
,
[
best_hint_index
,
None
,
None
]]]
elif
n_hints
==
2
:
elif
n_hints
==
2
:
best_hint
=
local_hints
[
answer
]
.
values
()[
0
][
0
]
best_hint
=
local_hints
[
signature
]
.
values
()[
0
][
0
]
best_hint_index
=
local_hints
[
answer
]
.
keys
()[
0
]
best_hint_index
=
local_hints
[
signature
]
.
keys
()[
0
]
rand_hint_1
=
local_hints
[
answer
]
.
values
()[
1
][
0
]
rand_hint_1
=
local_hints
[
signature
]
.
values
()[
1
][
0
]
hint_index_1
=
local_hints
[
answer
]
.
keys
()[
1
]
hint_index_1
=
local_hints
[
signature
]
.
keys
()[
1
]
rand_hint_2
=
''
rand_hint_2
=
''
self
.
previous_answers
+=
[[
answer
,
[
best_hint_index
,
hint_index_1
,
None
]]]
self
.
previous_answers
+=
[[
answer
,
[
best_hint_index
,
hint_index_1
,
None
]]]
else
:
else
:
(
hint_index_1
,
rand_hint_1
),
(
hint_index_2
,
rand_hint_2
)
=
\
(
hint_index_1
,
rand_hint_1
),
(
hint_index_2
,
rand_hint_2
)
=
\
random
.
sample
(
local_hints
[
answer
]
.
items
(),
2
)
random
.
sample
(
local_hints
[
signature
]
.
items
(),
2
)
rand_hint_1
=
rand_hint_1
[
0
]
rand_hint_1
=
rand_hint_1
[
0
]
rand_hint_2
=
rand_hint_2
[
0
]
rand_hint_2
=
rand_hint_2
[
0
]
self
.
previous_answers
+=
[[
answer
,
[
best_hint_index
,
hint_index_1
,
hint_index_2
]]]
self
.
previous_answers
+=
[[
answer
,
[
best_hint_index
,
hint_index_1
,
hint_index_2
]]]
...
@@ -206,12 +257,13 @@ class CrowdsourceHinterModule(CrowdsourceHinterFields, XModule):
...
@@ -206,12 +257,13 @@ class CrowdsourceHinterModule(CrowdsourceHinterFields, XModule):
answer
,
hints_offered
=
self
.
previous_answers
[
i
]
answer
,
hints_offered
=
self
.
previous_answers
[
i
]
if
answer
not
in
answer_to_hints
:
if
answer
not
in
answer_to_hints
:
answer_to_hints
[
answer
]
=
{}
answer_to_hints
[
answer
]
=
{}
if
answer
in
self
.
hints
:
signature
=
self
.
answer_signature
(
answer
)
if
signature
in
self
.
hints
:
# Go through each hint, and add to index_to_hints
# Go through each hint, and add to index_to_hints
for
hint_id
in
hints_offered
:
for
hint_id
in
hints_offered
:
if
(
hint_id
is
not
None
)
and
(
hint_id
not
in
answer_to_hints
[
answer
]):
if
(
hint_id
is
not
None
)
and
(
hint_id
not
in
answer_to_hints
[
signature
]):
try
:
try
:
answer_to_hints
[
answer
][
hint_id
]
=
self
.
hints
[
answer
][
str
(
hint_id
)][
0
]
answer_to_hints
[
answer
][
hint_id
]
=
self
.
hints
[
signature
][
str
(
hint_id
)][
0
]
except
KeyError
:
except
KeyError
:
# Sometimes, the hint that a user saw will have been deleted by the instructor.
# Sometimes, the hint that a user saw will have been deleted by the instructor.
continue
continue
...
@@ -234,11 +286,12 @@ class CrowdsourceHinterModule(CrowdsourceHinterFields, XModule):
...
@@ -234,11 +286,12 @@ class CrowdsourceHinterModule(CrowdsourceHinterFields, XModule):
if
self
.
user_voted
:
if
self
.
user_voted
:
return
json
.
dumps
({
'contents'
:
'Sorry, but you have already voted!'
})
return
json
.
dumps
({
'contents'
:
'Sorry, but you have already voted!'
})
ans
=
data
[
'answer'
]
ans
=
data
[
'answer'
]
signature
=
self
.
answer_signature
(
ans
)
hint_pk
=
str
(
data
[
'hint'
])
hint_pk
=
str
(
data
[
'hint'
])
pk_list
=
json
.
loads
(
data
[
'pk_list'
])
pk_list
=
json
.
loads
(
data
[
'pk_list'
])
# We use temp_dict because we need to do a direct write for the database to update.
# We use temp_dict because we need to do a direct write for the database to update.
temp_dict
=
self
.
hints
temp_dict
=
self
.
hints
temp_dict
[
ans
][
hint_pk
][
1
]
+=
1
temp_dict
[
signature
][
hint_pk
][
1
]
+=
1
self
.
hints
=
temp_dict
self
.
hints
=
temp_dict
# Don't let the user vote again!
# Don't let the user vote again!
self
.
user_voted
=
True
self
.
user_voted
=
True
...
@@ -246,7 +299,7 @@ class CrowdsourceHinterModule(CrowdsourceHinterFields, XModule):
...
@@ -246,7 +299,7 @@ class CrowdsourceHinterModule(CrowdsourceHinterFields, XModule):
# Return a list of how many votes each hint got.
# Return a list of how many votes each hint got.
hint_and_votes
=
[]
hint_and_votes
=
[]
for
vote_pk
in
pk_list
:
for
vote_pk
in
pk_list
:
hint_and_votes
.
append
(
temp_dict
[
ans
][
str
(
vote_pk
)])
hint_and_votes
.
append
(
temp_dict
[
signature
][
str
(
vote_pk
)])
hint_and_votes
.
sort
(
key
=
lambda
pair
:
pair
[
1
],
reverse
=
True
)
hint_and_votes
.
sort
(
key
=
lambda
pair
:
pair
[
1
],
reverse
=
True
)
# Reset self.previous_answers.
# Reset self.previous_answers.
...
@@ -266,6 +319,7 @@ class CrowdsourceHinterModule(CrowdsourceHinterFields, XModule):
...
@@ -266,6 +319,7 @@ class CrowdsourceHinterModule(CrowdsourceHinterFields, XModule):
# Do html escaping. Perhaps in the future do profanity filtering, etc. as well.
# Do html escaping. Perhaps in the future do profanity filtering, etc. as well.
hint
=
escape
(
data
[
'hint'
])
hint
=
escape
(
data
[
'hint'
])
answer
=
data
[
'answer'
]
answer
=
data
[
'answer'
]
signature
=
self
.
answer_signature
(
answer
)
# Only allow a student to vote or submit a hint once.
# Only allow a student to vote or submit a hint once.
if
self
.
user_voted
:
if
self
.
user_voted
:
return
{
'message'
:
'Sorry, but you have already voted!'
}
return
{
'message'
:
'Sorry, but you have already voted!'
}
...
@@ -276,9 +330,15 @@ class CrowdsourceHinterModule(CrowdsourceHinterFields, XModule):
...
@@ -276,9 +330,15 @@ class CrowdsourceHinterModule(CrowdsourceHinterFields, XModule):
else
:
else
:
temp_dict
=
self
.
hints
temp_dict
=
self
.
hints
if
answer
in
temp_dict
:
if
answer
in
temp_dict
:
temp_dict
[
answer
][
str
(
self
.
hint_pk
)]
=
[
hint
,
1
]
# With one vote (the user himself).
temp_dict
[
signature
][
str
(
self
.
hint_pk
)]
=
[
hint
,
1
]
# With one vote (the user himself).
else
:
else
:
temp_dict
[
answer
]
=
{
str
(
self
.
hint_pk
):
[
hint
,
1
]}
temp_dict
[
signature
]
=
{
str
(
self
.
hint_pk
):
[
hint
,
1
]}
# Add the signature to signature_to_ans, if it's not there yet.
# This allows instructors to see a human-readable answer that corresponds to each signature.
if
answer
not
in
self
.
signature_to_ans
:
local_sta
=
self
.
signature_to_ans
local_sta
[
signature
]
=
answer
self
.
signature_to_ans
=
local_sta
self
.
hint_pk
+=
1
self
.
hint_pk
+=
1
if
self
.
moderate
==
'True'
:
if
self
.
moderate
==
'True'
:
self
.
mod_queue
=
temp_dict
self
.
mod_queue
=
temp_dict
...
...
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