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
199b6325
Commit
199b6325
authored
Jul 11, 2013
by
Felix Sun
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Crowdsourced hinter now supports formula responses. Tests still broken.
parent
b88c6b8d
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
116 additions
and
52 deletions
+116
-52
common/lib/xmodule/xmodule/crowdsource_hinter.py
+22
-11
common/lib/xmodule/xmodule/js/src/crowdsource_hinter/display.coffee
+5
-4
common/templates/hinter_display.html
+12
-3
lms/djangoapps/instructor/hint_manager.py
+72
-31
lms/templates/instructor/hint_manager.html
+1
-1
lms/templates/instructor/hint_manager_inner.html
+4
-2
No files found.
common/lib/xmodule/xmodule/crowdsource_hinter.py
View file @
199b6325
...
...
@@ -13,7 +13,7 @@ from pkg_resources import resource_string
from
lxml
import
etree
from
xmodule.x_module
import
XModule
from
xmodule.
xml_module
import
Xml
Descriptor
from
xmodule.
raw_module
import
Raw
Descriptor
from
xblock.core
import
Scope
,
String
,
Integer
,
Boolean
,
Dict
,
List
from
capa.responsetypes
import
FormulaResponse
,
StudentInputError
...
...
@@ -150,10 +150,10 @@ class CrowdsourceHinterModule(CrowdsourceHinterFields, XModule):
# 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 togethe
r.
return
''
# I'm not sure what's the best thing to do here.
I'm returning
#
None, for now, so that the calling function has a chance to catch
#
the error without having to import StudentInputErro
r.
return
None
return
out
def
handle_ajax
(
self
,
dispatch
,
data
):
...
...
@@ -197,6 +197,10 @@ class CrowdsourceHinterModule(CrowdsourceHinterFields, XModule):
return
# Make a signature of the answer, for formula responses.
signature
=
self
.
answer_signature
(
answer
)
if
signature
==
None
:
# Sometimes, signature conversion may fail.
log
.
exception
(
'Signature conversion failed: '
+
str
(
answer
))
return
# Look for a hint to give.
# 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.)
...
...
@@ -261,7 +265,7 @@ class CrowdsourceHinterModule(CrowdsourceHinterFields, XModule):
if
signature
in
self
.
hints
:
# Go through each hint, and add to index_to_hints
for
hint_id
in
hints_offered
:
if
(
hint_id
is
not
None
)
and
(
hint_id
not
in
answer_to_hints
[
signature
]):
if
(
hint_id
is
not
None
)
and
(
hint_id
not
in
answer_to_hints
[
answer
]):
try
:
answer_to_hints
[
answer
][
hint_id
]
=
self
.
hints
[
signature
][
str
(
hint_id
)][
0
]
except
KeyError
:
...
...
@@ -335,10 +339,7 @@ class CrowdsourceHinterModule(CrowdsourceHinterFields, XModule):
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
.
add_signature
(
signature
,
answer
)
self
.
hint_pk
+=
1
if
self
.
moderate
==
'True'
:
self
.
mod_queue
=
temp_dict
...
...
@@ -349,8 +350,18 @@ class CrowdsourceHinterModule(CrowdsourceHinterFields, XModule):
self
.
previous_answers
=
[]
return
{
'message'
:
'Thank you for your hint!'
}
def
add_signature
(
self
,
signature
,
answer
):
"""
Add a signature to self.signature_to_ans. If the signature already
exists, do nothing.
"""
if
signature
not
in
self
.
signature_to_ans
:
local_sta
=
self
.
signature_to_ans
local_sta
[
signature
]
=
answer
self
.
signature_to_ans
=
local_sta
class
CrowdsourceHinterDescriptor
(
CrowdsourceHinterFields
,
Xml
Descriptor
):
class
CrowdsourceHinterDescriptor
(
CrowdsourceHinterFields
,
Raw
Descriptor
):
module_class
=
CrowdsourceHinterModule
stores_state
=
True
...
...
common/lib/xmodule/xmodule/js/src/crowdsource_hinter/display.coffee
View file @
199b6325
...
...
@@ -48,8 +48,8 @@ class @Hinter
vote
:
(
eventObj
)
=>
target
=
@
$
(
eventObj
.
currentTarget
)
parent_div
_selector
=
'#previous-answer-'
+
@
jq_escape
(
target
.
attr
(
'data-answer'
)
)
all_pks
=
@
$
(
parent_div_selector
)
.
attr
(
'data-all-pks'
)
parent_div
=
$
(
'.previous-answer[data-answer="'
+
target
.
attr
(
'data-answer'
)
+
'"]'
)
all_pks
=
parent_div
.
attr
(
'data-all-pks'
)
console
.
debug
(
all_pks
)
post_json
=
{
'answer'
:
target
.
attr
(
'data-answer'
),
'hint'
:
target
.
data
(
'hintno'
),
'pk_list'
:
all_pks
}
$
.
postWithPrefix
"
#{
@
url
}
/vote"
,
post_json
,
(
response
)
=>
...
...
@@ -57,8 +57,9 @@ class @Hinter
submit_hint
:
(
eventObj
)
=>
target
=
@
$
(
eventObj
.
currentTarget
)
textarea_id
=
'#custom-hint-'
+
@
jq_escape
(
target
.
attr
(
'data-answer'
))
post_json
=
{
'answer'
:
target
.
attr
(
'data-answer'
),
'hint'
:
@
$
(
textarea_id
).
val
()}
textarea
=
$
(
'.custom-hint[data-answer="'
+
target
.
attr
(
'data-answer'
)
+
'"]'
)
console
.
debug
(
textarea
)
post_json
=
{
'answer'
:
target
.
attr
(
'data-answer'
),
'hint'
:
@
$
(
textarea
).
val
()}
$
.
postWithPrefix
"
#{
@
url
}
/submit_hint"
,
post_json
,
(
response
)
=>
@
render
(
response
.
contents
)
...
...
common/templates/hinter_display.html
View file @
199b6325
...
...
@@ -23,10 +23,19 @@
Help your classmates by writing hints for this problem. Start by picking one of your previous incorrect answers from below:
</p>
<
%
def
unspace
(
string
)
:
"""
HTML
id
'
s
can
'
t
have
spaces
in
them
.
This
little
function
removes
spaces
.
"""
return
''.
join
(
string
.
split
())
%
>
<div
id=
"answer-tabs"
>
<ul>
% for answer in answer_to_hints:
<li><a
href=
"#previous-answer-${
answer}"
>
${answer}
</a></li>
<li><a
href=
"#previous-answer-${
unspace(answer)}"
>
${answer}
</a></li>
% endfor
</ul>
...
...
@@ -35,7 +44,7 @@
import
json
all_pks =
json.dumps(pk_dict.keys())
%
>
<div
class =
"previous-answer"
id=
"previous-answer-${answer}"
data-all-pks=
'${all_pks}'
>
<div
class =
"previous-answer"
id=
"previous-answer-${
unspace(answer)}"
data-answer=
"${
answer}"
data-all-pks=
'${all_pks}'
>
<div
class =
"hint-inner-container"
>
% if len(pk_dict) > 0:
<p>
...
...
@@ -54,7 +63,7 @@
<p>
What hint would you give a student who made the same mistake you did? Please don't give away the answer.
</p>
<textarea
cols=
"50"
class=
"custom-hint"
id=
"custom-hint-
${answer}"
>
<textarea
cols=
"50"
class=
"custom-hint"
data-answer=
"
${answer}"
>
What would you say to help someone who got this wrong answer?
(Don't give away the answer, please.)
</textarea>
...
...
lms/djangoapps/instructor/hint_manager.py
View file @
199b6325
...
...
@@ -10,11 +10,14 @@ import re
from
django.http
import
HttpResponse
,
Http404
from
django_future.csrf
import
ensure_csrf_cookie
from
django.core.exceptions
import
ObjectDoesNotExist
from
mitxmako.shortcuts
import
render_to_response
,
render_to_string
from
courseware.courses
import
get_course_with_access
from
courseware.models
import
XModuleContentField
from
courseware.module_render
import
get_module
from
courseware.model_data
import
ModelDataCache
from
xmodule.modulestore
import
Location
from
xmodule.modulestore.django
import
modulestore
...
...
@@ -28,24 +31,29 @@ def hint_manager(request, course_id):
return
HttpResponse
(
out
)
if
request
.
method
==
'GET'
:
out
=
get_hints
(
request
,
course_id
,
'mod_queue'
)
return
render_to_response
(
'courseware/hint_manager.html'
,
out
)
out
.
update
({
'error'
:
''
})
return
render_to_response
(
'instructor/hint_manager.html'
,
out
)
field
=
request
.
POST
[
'field'
]
if
not
(
field
==
'mod_queue'
or
field
==
'hints'
):
# Invalid field. (Don't let users continue - they may overwrite other db's)
out
=
'Error in hint manager - an invalid field was accessed.'
return
HttpResponse
(
out
)
if
request
.
POST
[
'op'
]
==
'delete hints'
:
delete_hints
(
request
,
course_id
,
field
)
if
request
.
POST
[
'op'
]
==
'switch fields'
:
pass
if
request
.
POST
[
'op'
]
==
'change votes'
:
change_votes
(
request
,
course_id
,
field
)
if
request
.
POST
[
'op'
]
==
'add hint'
:
add_hint
(
request
,
course_id
,
field
)
if
request
.
POST
[
'op'
]
==
'approve'
:
approve
(
request
,
course_id
,
field
)
rendered_html
=
render_to_string
(
'courseware/hint_manager_inner.html'
,
get_hints
(
request
,
course_id
,
field
))
switch_dict
=
{
'delete hints'
:
delete_hints
,
'switch fields'
:
lambda
*
args
:
None
,
# Takes any number of arguments, returns None.
'change votes'
:
change_votes
,
'add hint'
:
add_hint
,
'approve'
:
approve
,
}
# Do the operation requested, and collect any error messages.
error_text
=
switch_dict
[
request
.
POST
[
'op'
]](
request
,
course_id
,
field
)
if
error_text
is
None
:
error_text
=
''
render_dict
=
get_hints
(
request
,
course_id
,
field
)
render_dict
.
update
({
'error'
:
error_text
})
rendered_html
=
render_to_string
(
'instructor/hint_manager_inner.html'
,
render_dict
)
return
HttpResponse
(
json
.
dumps
({
'success'
:
True
,
'contents'
:
rendered_html
}))
...
...
@@ -106,8 +114,27 @@ def get_hints(request, course_id, field):
# Put all non-numerical answers first.
return
float
(
'-inf'
)
# Answer list contains [answer, dict_of_hints] pairs.
answer_list
=
sorted
(
json
.
loads
(
hints_by_problem
.
value
)
.
items
(),
key
=
answer_sorter
)
# Find the signature to answer converter for this problem. Sometimes,
# it doesn't exist; just assume that the signatures are the answers.
try
:
signature_to_ans
=
XModuleContentField
.
objects
.
get
(
field_name
=
'signature_to_ans'
,
definition_id__regex
=
chopped_id
)
signature_to_ans
=
json
.
loads
(
signature_to_ans
.
value
)
except
ObjectDoesNotExist
:
signature_to_ans
=
{}
signatures_dict
=
json
.
loads
(
hints_by_problem
.
value
)
unsorted
=
[]
for
signature
,
dict_of_hints
in
signatures_dict
.
items
():
if
signature
in
signature_to_ans
:
ans_txt
=
signature_to_ans
[
signature
]
else
:
ans_txt
=
signature
unsorted
.
append
([
signature
,
ans_txt
,
dict_of_hints
])
# Answer list contains [signature, answer, dict_of_hints] sub-lists.
answer_list
=
sorted
(
unsorted
,
key
=
answer_sorter
)
big_out_dict
[
hints_by_problem
.
definition_id
]
=
answer_list
render_dict
=
{
'field'
:
field
,
...
...
@@ -138,7 +165,7 @@ def delete_hints(request, course_id, field):
Deletes the hints specified.
`request.POST` contains some fields keyed by integers. Each such field contains a
[problem_defn_id,
answer
, pk] tuple. These tuples specify the hints to be deleted.
[problem_defn_id,
signature
, pk] tuple. These tuples specify the hints to be deleted.
Example `request.POST`:
{'op': 'delete_hints',
...
...
@@ -150,12 +177,12 @@ def delete_hints(request, course_id, field):
for
key
in
request
.
POST
:
if
key
==
'op'
or
key
==
'field'
:
continue
problem_id
,
answer
,
pk
=
request
.
POST
.
getlist
(
key
)
problem_id
,
signature
,
pk
=
request
.
POST
.
getlist
(
key
)
# Can be optimized - sort the delete list by problem_id, and load each problem
# from the database only once.
this_problem
=
XModuleContentField
.
objects
.
get
(
field_name
=
field
,
definition_id
=
problem_id
)
problem_dict
=
json
.
loads
(
this_problem
.
value
)
del
problem_dict
[
answer
][
pk
]
del
problem_dict
[
signature
][
pk
]
this_problem
.
value
=
json
.
dumps
(
problem_dict
)
this_problem
.
save
()
...
...
@@ -164,18 +191,18 @@ def change_votes(request, course_id, field):
"""
Updates the number of votes.
The numbered fields of `request.POST` contain [problem_id,
answer
, pk, new_votes] tuples.
The numbered fields of `request.POST` contain [problem_id,
signature
, pk, new_votes] tuples.
- Very similar to `delete_hints`. Is there a way to merge them? Nah, too complicated.
"""
for
key
in
request
.
POST
:
if
key
==
'op'
or
key
==
'field'
:
continue
problem_id
,
answer
,
pk
,
new_votes
=
request
.
POST
.
getlist
(
key
)
problem_id
,
signature
,
pk
,
new_votes
=
request
.
POST
.
getlist
(
key
)
this_problem
=
XModuleContentField
.
objects
.
get
(
field_name
=
field
,
definition_id
=
problem_id
)
problem_dict
=
json
.
loads
(
this_problem
.
value
)
# problem_dict[
answer
][pk] points to a [hint_text, #votes] pair.
problem_dict
[
answer
][
pk
][
1
]
=
int
(
new_votes
)
# problem_dict[
signature
][pk] points to a [hint_text, #votes] pair.
problem_dict
[
signature
][
pk
][
1
]
=
int
(
new_votes
)
this_problem
.
value
=
json
.
dumps
(
problem_dict
)
this_problem
.
save
()
...
...
@@ -187,6 +214,7 @@ def add_hint(request, course_id, field):
field
problem - The problem id
answer - The answer to which a hint will be added
- Needs to be converted into signature first.
hint - The text of the hint
"""
...
...
@@ -200,10 +228,23 @@ def add_hint(request, course_id, field):
hint_pk_entry
.
value
=
this_pk
+
1
hint_pk_entry
.
save
()
# Make signature. This is really annoying, but I don't see
# any alternative :(
loc
=
Location
(
problem_id
)
descriptors
=
modulestore
()
.
get_items
(
loc
)
m_d_c
=
ModelDataCache
(
descriptors
,
course_id
,
request
.
user
)
hinter_module
=
get_module
(
request
.
user
,
request
,
loc
,
m_d_c
,
course_id
)
signature
=
hinter_module
.
answer_signature
(
answer
)
if
signature
is
None
:
# Signature generation failed.
# We should probably return an error message, too... working on that.
return
'Error - your answer could not be parsed as a formula expression.'
hinter_module
.
add_signature
(
signature
,
answer
)
problem_dict
=
json
.
loads
(
this_problem
.
value
)
if
answer
not
in
problem_dict
:
problem_dict
[
answer
]
=
{}
problem_dict
[
answer
][
this_pk
]
=
[
hint_text
,
1
]
if
signature
not
in
problem_dict
:
problem_dict
[
signature
]
=
{}
problem_dict
[
signature
][
this_pk
]
=
[
hint_text
,
1
]
this_problem
.
value
=
json
.
dumps
(
problem_dict
)
this_problem
.
save
()
...
...
@@ -213,26 +254,26 @@ def approve(request, course_id, field):
Approve a list of hints, moving them from the mod_queue to the real
hint list. POST:
op, field
(some number) -> [problem,
answer
, pk]
(some number) -> [problem,
signature
, pk]
"""
for
key
in
request
.
POST
:
if
key
==
'op'
or
key
==
'field'
:
continue
problem_id
,
answer
,
pk
=
request
.
POST
.
getlist
(
key
)
problem_id
,
signature
,
pk
=
request
.
POST
.
getlist
(
key
)
# Can be optimized - sort the delete list by problem_id, and load each problem
# from the database only once.
problem_in_mod
=
XModuleContentField
.
objects
.
get
(
field_name
=
field
,
definition_id
=
problem_id
)
problem_dict
=
json
.
loads
(
problem_in_mod
.
value
)
hint_to_move
=
problem_dict
[
answer
][
pk
]
del
problem_dict
[
answer
][
pk
]
hint_to_move
=
problem_dict
[
signature
][
pk
]
del
problem_dict
[
signature
][
pk
]
problem_in_mod
.
value
=
json
.
dumps
(
problem_dict
)
problem_in_mod
.
save
()
problem_in_hints
=
XModuleContentField
.
objects
.
get
(
field_name
=
'hints'
,
definition_id
=
problem_id
)
problem_dict
=
json
.
loads
(
problem_in_hints
.
value
)
if
answer
not
in
problem_dict
:
problem_dict
[
answer
]
=
{}
problem_dict
[
answer
][
pk
]
=
hint_to_move
if
signature
not
in
problem_dict
:
problem_dict
[
signature
]
=
{}
problem_dict
[
signature
][
pk
]
=
hint_to_move
problem_in_hints
.
value
=
json
.
dumps
(
problem_dict
)
problem_in_hints
.
save
()
lms/templates/
courseware
/hint_manager.html
→
lms/templates/
instructor
/hint_manager.html
View file @
199b6325
<
%
inherit
file=
"/main.html"
/>
<
%
namespace
name=
'static'
file=
'/static_content.html'
/>
<
%
namespace
name=
"content"
file=
"/
courseware
/hint_manager_inner.html"
/>
<
%
namespace
name=
"content"
file=
"/
instructor
/hint_manager_inner.html"
/>
<
%
block
name=
"headextra"
>
...
...
lms/templates/
courseware
/hint_manager_inner.html
→
lms/templates/
instructor
/hint_manager_inner.html
View file @
199b6325
...
...
@@ -4,15 +4,16 @@
<h1>
${field_label}
</h1>
Switch to
<a
id=
"switch-fields"
other-field=
"${other_field}"
>
${other_field_label}
</a>
<p
style=
"color:red"
>
${error}
</p>
% for definition_id in all_hints:
<h2>
Problem: ${id_to_name[definition_id]}
</h2>
% for answer, hint_dict in all_hints[definition_id]:
% for
signature,
answer, hint_dict in all_hints[definition_id]:
% if len(hint_dict) > 0:
<h4>
Answer: ${answer}
</h4><div
style=
"background-color:#EEEEEE"
>
% endif
% for pk, hint in hint_dict.items():
<p
data-problem=
"${definition_id}"
data-pk=
"${pk}"
data-answer=
"${
answer
}"
>
<p
data-problem=
"${definition_id}"
data-pk=
"${pk}"
data-answer=
"${
signature
}"
>
<input
class=
"hint-select"
type=
"checkbox"
/>
${hint[0]}
<br
/>
Votes:
<input
type=
"text"
class=
"votes"
value=
"${str(hint[1])}"
style=
"font-size:12px; height:20px; width:50px"
></input>
...
...
@@ -36,6 +37,7 @@ Switch to <a id="switch-fields" other-field="${other_field}">${other_field_label
<br
/>
% endfor
<p
style=
"color:red"
>
${error}
</p>
<button
id=
"hint-delete"
>
Delete selected
</button>
<button
id=
"update-votes"
>
Update votes
</button>
% if field == 'mod_queue':
...
...
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