Commit c79ca38f by Felix Sun

Moved the hinter rendering onto a mako template.

Hinter now displays vote count after voting.

Began testing templates.
parent bb922ed8
...@@ -23,31 +23,31 @@ log = logging.getLogger(__name__) ...@@ -23,31 +23,31 @@ log = logging.getLogger(__name__)
class CrowdsourceHinterFields(object): class CrowdsourceHinterFields(object):
has_children = True has_children = True
hints = Dict(help='''A dictionary mapping answers to lists of [hint, number_of_votes] pairs. hints = Dict(help="""A dictionary mapping answers to lists of [hint, number_of_votes] pairs.
''', scope=Scope.content, default= {}) """, scope=Scope.content, default= {})
previous_answers = List(help='''A list of previous answers this student made to this problem. previous_answers = List(help="""A list of previous answers this student made to this problem.
Of the form (answer, (hint_id_1, hint_id_2, hint_id_3)) for each problem. hint_id's are Of the form (answer, (hint_id_1, hint_id_2, hint_id_3)) for each problem. hint_id's are
None if the hint was not given.''', None if the hint was not given.""",
scope=Scope.user_state, default=[]) scope=Scope.user_state, default=[])
user_voted = Boolean(help='Specifies if the user has voted on this problem or not.', user_voted = Boolean(help='Specifies if the user has voted on this problem or not.',
scope=Scope.user_state, default=False) scope=Scope.user_state, default=False)
moderate = String(help='''If True, then all hints must be approved by staff before moderate = String(help="""If True, then all hints must be approved by staff before
becoming visible. becoming visible.
This field is automatically populated from the xml metadata.''', scope=Scope.content, This field is automatically populated from the xml metadata.""", scope=Scope.content,
default='False') default='False')
mod_queue = Dict(help='''Contains hints that have not been approved by the staff yet. Structured mod_queue = Dict(help="""Contains hints that have not been approved by the staff yet. Structured
identically to the hints dictionary.''', scope=Scope.content, default={}) identically to the hints dictionary.""", scope=Scope.content, 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)
class CrowdsourceHinterModule(CrowdsourceHinterFields, XModule): class CrowdsourceHinterModule(CrowdsourceHinterFields, XModule):
''' An Xmodule that makes crowdsourced hints. """ An Xmodule that makes crowdsourced hints.
''' """
icon_class = 'crowdsource_hinter' icon_class = 'crowdsource_hinter'
js = {'coffee': [resource_string(__name__, 'js/src/crowdsource_hinter/display.coffee'), js = {'coffee': [resource_string(__name__, 'js/src/crowdsource_hinter/display.coffee'),
...@@ -61,10 +61,10 @@ class CrowdsourceHinterModule(CrowdsourceHinterFields, XModule): ...@@ -61,10 +61,10 @@ class CrowdsourceHinterModule(CrowdsourceHinterFields, XModule):
def get_html(self): def get_html(self):
''' """
Does a regular expression find and replace to change the AJAX url. Does a regular expression find and replace to change the AJAX url.
- Dependent on lon-capa problem. - Dependent on lon-capa problem.
''' """
# Reset the user vote, for debugging only! Remove for prod. # Reset the user vote, for debugging only! Remove for prod.
self.user_voted = False self.user_voted = False
# You are invited to guess what the lines below do :) # You are invited to guess what the lines below do :)
...@@ -82,10 +82,10 @@ class CrowdsourceHinterModule(CrowdsourceHinterFields, XModule): ...@@ -82,10 +82,10 @@ class CrowdsourceHinterModule(CrowdsourceHinterFields, XModule):
return out return out
def capa_make_answer_hashable(self, answer): def capa_make_answer_hashable(self, answer):
''' """
Capa answer format: dict[problem name] -> [list of answers] Capa answer format: dict[problem name] -> [list of answers]
Output format: ((problem name, (answers))) Output format: ((problem name, (answers)))
''' """
out = [] out = []
for problem, a in answer.items(): for problem, a in answer.items():
out.append((problem, tuple(a))) out.append((problem, tuple(a)))
...@@ -93,18 +93,18 @@ class CrowdsourceHinterModule(CrowdsourceHinterFields, XModule): ...@@ -93,18 +93,18 @@ class CrowdsourceHinterModule(CrowdsourceHinterFields, XModule):
def ans_to_text(self, answer): def ans_to_text(self, answer):
''' """
Converts capa answer format to a string representation Converts capa answer format to a string representation
of the answer. of the answer.
-Lon-capa dependent. -Lon-capa dependent.
''' """
return str(float(answer.values()[0])) return str(float(answer.values()[0]))
def handle_ajax(self, dispatch, get): def handle_ajax(self, dispatch, get):
''' """
This is the landing method for AJAX calls. This is the landing method for AJAX calls.
''' """
if dispatch == 'get_hint': if dispatch == 'get_hint':
out = self.get_hint(get) out = self.get_hint(get)
if dispatch == 'get_feedback': if dispatch == 'get_feedback':
...@@ -122,33 +122,35 @@ class CrowdsourceHinterModule(CrowdsourceHinterFields, XModule): ...@@ -122,33 +122,35 @@ class CrowdsourceHinterModule(CrowdsourceHinterFields, XModule):
def get_hint(self, get): def get_hint(self, get):
''' """
The student got the incorrect answer found in get. Give him a hint. The student got the incorrect answer found in get. Give him a hint.
''' """
answer = self.ans_to_text(get) answer = self.ans_to_text(get)
# Look for a hint to give. # Look for a hint to give.
if (answer not in self.hints) or (len(self.hints[answer]) == 0): # Make a local copy of self.hints - this means we only need to do one json unpacking.
local_hints = self.hints
if (answer not in local_hints) or (len(local_hints[answer]) == 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(self.hints[answer]) n_hints = len(local_hints[answer])
best_hint_index = max(self.hints[answer], key=lambda key: self.hints[answer][key][1]) best_hint_index = max(local_hints[answer], key=lambda key: local_hints[answer][key][1])
best_hint = self.hints[answer][best_hint_index][0] best_hint = local_hints[answer][best_hint_index][0]
if len(self.hints[answer]) == 1: if len(local_hints[answer]) == 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 = self.hints[answer].values()[0][0] best_hint = local_hints[answer].values()[0][0]
best_hint_index = self.hints[answer].keys()[0] best_hint_index = local_hints[answer].keys()[0]
rand_hint_1 = self.hints[answer].values()[1][0] rand_hint_1 = local_hints[answer].values()[1][0]
hint_index_1 = self.hints[answer].keys()[1] hint_index_1 = local_hints[answer].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(self.hints[answer].items(), 2) random.sample(local_hints[answer].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))]
...@@ -159,9 +161,9 @@ class CrowdsourceHinterModule(CrowdsourceHinterFields, XModule): ...@@ -159,9 +161,9 @@ class CrowdsourceHinterModule(CrowdsourceHinterFields, XModule):
'answer': answer} 'answer': answer}
def get_feedback(self, get): def get_feedback(self, get):
''' """
The student got it correct. Ask him to vote on hints, or submit a hint. The student got it correct. Ask him to vote on hints, or submit a hint.
''' """
# The student got it right. # The student got it right.
# Did he submit at least one wrong answer? # Did he submit at least one wrong answer?
out = '' out = ''
...@@ -181,11 +183,10 @@ class CrowdsourceHinterModule(CrowdsourceHinterFields, XModule): ...@@ -181,11 +183,10 @@ class CrowdsourceHinterModule(CrowdsourceHinterFields, XModule):
index_to_hints[i] = [] index_to_hints[i] = []
index_to_answer[i] = answer index_to_answer[i] = answer
if answer in self.hints: if answer in self.hints:
# Add each hint to the html string, with a vote button.
for hint_id in hints_offered: for hint_id in hints_offered:
if hint_id != None: if hint_id != None:
try: try:
index_to_hints[i].append((self.hints[answer][hint_id][0], hint_id)) index_to_hints[i].append((self.hints[answer][str(hint_id)][0], hint_id))
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
...@@ -194,12 +195,12 @@ class CrowdsourceHinterModule(CrowdsourceHinterFields, XModule): ...@@ -194,12 +195,12 @@ class CrowdsourceHinterModule(CrowdsourceHinterFields, XModule):
def tally_vote(self, get): def tally_vote(self, get):
''' """
Tally a user's vote on his favorite hint. Tally a user's vote on his favorite hint.
get: get:
'answer': ans_no (index in previous_answers) 'answer': ans_no (index in previous_answers)
'hint': hint_no 'hint': hint_no
''' """
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_no = int(get['answer']) ans_no = int(get['answer'])
...@@ -211,19 +212,25 @@ class CrowdsourceHinterModule(CrowdsourceHinterFields, XModule): ...@@ -211,19 +212,25 @@ class CrowdsourceHinterModule(CrowdsourceHinterFields, XModule):
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
# Return a list of how many votes each hint got.
hint_and_votes = []
for hint_no in self.previous_answers[ans_no][1]:
if hint_no == None:
continue
hint_and_votes.append(temp_dict[answer][str(hint_no)])
# Reset self.previous_answers. # Reset self.previous_answers.
self.previous_answers = [] self.previous_answers = []
# In the future, return a list of how many votes each hint got, maybe? return {'hint_and_votes': hint_and_votes}
return {'message': 'Congrats, you\'ve voted!'}
def submit_hint(self, get): def submit_hint(self, get):
''' """
Take a hint submission and add it to the database. Take a hint submission and add it to the database.
get: get:
'answer': answer index in previous_answers 'answer': answer index in previous_answers
'hint': text of the new hint that the user is adding 'hint': text of the new hint that the user is adding
''' """
# 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(get['hint']) hint = escape(get['hint'])
answer = self.previous_answers[int(get['answer'])][0] answer = self.previous_answers[int(get['answer'])][0]
...@@ -251,11 +258,11 @@ class CrowdsourceHinterModule(CrowdsourceHinterFields, XModule): ...@@ -251,11 +258,11 @@ class CrowdsourceHinterModule(CrowdsourceHinterFields, XModule):
def delete_hint(self, answer, hint_id): def delete_hint(self, answer, hint_id):
''' """
From the answer, delete the hint with hint_id. From the answer, delete the hint with hint_id.
Not designed to be accessed via POST request, for now. Not designed to be accessed via POST request, for now.
-LIKELY DEPRECATED. -LIKELY DEPRECATED.
''' """
temp_hints = self.hints temp_hints = self.hints
del temp_hints[answer][str(hint_id)] del temp_hints[answer][str(hint_id)]
self.hints = temp_hints self.hints = temp_hints
......
...@@ -13,7 +13,6 @@ class @Hinter ...@@ -13,7 +13,6 @@ class @Hinter
# request. # request.
answers = data[0] answers = data[0]
response = data[1] response = data[1]
console.debug(response)
if response.search(/class="correct/) == -1 if response.search(/class="correct/) == -1
# Incorrect. Get hints. # Incorrect. Get hints.
$.postWithPrefix "#{@url}/get_hint", answers, (response) => $.postWithPrefix "#{@url}/get_hint", answers, (response) =>
...@@ -29,9 +28,9 @@ class @Hinter ...@@ -29,9 +28,9 @@ class @Hinter
bind: => bind: =>
window.update_schematics() window.update_schematics()
@$('input.vote').click @vote @$('input.vote').click @vote
@$('#feedback-select').change @feedback_ui_change
@$('input.submit-hint').click @submit_hint @$('input.submit-hint').click @submit_hint
@$('.custom-hint').click @clear_default_text @$('.custom-hint').click @clear_default_text
@$('#answer-tabs').tabs({active: 0})
vote: (eventObj) => vote: (eventObj) =>
......
from mock import Mock, patch from mock import Mock, patch
import unittest import unittest
import copy import copy
import random
import xmodule import xmodule
from xmodule.crowdsource_hinter import CrowdsourceHinterModule from xmodule.crowdsource_hinter import CrowdsourceHinterModule
...@@ -13,12 +14,12 @@ from . import get_test_system ...@@ -13,12 +14,12 @@ from . import get_test_system
import json import json
class CHModuleFactory(object): class CHModuleFactory(object):
''' """
Helps us make a CrowdsourceHinterModule with the specified internal Helps us make a CrowdsourceHinterModule with the specified internal
state. state.
''' """
sample_problem_xml = ''' sample_problem_xml = """
<?xml version="1.0"?> <?xml version="1.0"?>
<crowdsource_hinter> <crowdsource_hinter>
<problem display_name="Numerical Input" markdown="A numerical input problem accepts a line of text input from the student, and evaluates the input for correctness based on its numerical value.&#10;&#10;The answer is correct if it is within a specified numerical tolerance of the expected answer.&#10;&#10;Enter the number of fingers on a human hand:&#10;= 5&#10;&#10;[explanation]&#10;If you look at your hand, you can count that you have five fingers. [explanation] " rerandomize="never" showanswer="finished"> <problem display_name="Numerical Input" markdown="A numerical input problem accepts a line of text input from the student, and evaluates the input for correctness based on its numerical value.&#10;&#10;The answer is correct if it is within a specified numerical tolerance of the expected answer.&#10;&#10;Enter the number of fingers on a human hand:&#10;= 5&#10;&#10;[explanation]&#10;If you look at your hand, you can count that you have five fingers. [explanation] " rerandomize="never" showanswer="finished">
...@@ -36,7 +37,7 @@ class CHModuleFactory(object): ...@@ -36,7 +37,7 @@ class CHModuleFactory(object):
</solution> </solution>
</problem> </problem>
</crowdsource_hinter> </crowdsource_hinter>
''' """
num = 0 num = 0
...@@ -91,102 +92,101 @@ class CHModuleFactory(object): ...@@ -91,102 +92,101 @@ class CHModuleFactory(object):
descriptor = Mock(weight="1") descriptor = Mock(weight="1")
system = get_test_system() system = get_test_system()
system.render_template = Mock(return_value="<div>Test Template HTML</div>")
module = CrowdsourceHinterModule(system, descriptor, model_data) module = CrowdsourceHinterModule(system, descriptor, model_data)
return module return module
class CrowdsourceHinterTest(unittest.TestCase): class CrowdsourceHinterTest(unittest.TestCase):
''' """
In the below tests, '24.0' represents a wrong answer, and '42.5' represents In the below tests, '24.0' represents a wrong answer, and '42.5' represents
a correct answer. a correct answer.
''' """
def test_gethint_0hint(self): def test_gethint_0hint(self):
''' """
Someone asks for a hint, when there's no hint to give. Someone asks for a hint, when there's no hint to give.
- Output should be blank. - Output should be blank.
- New entry should be added to previous_answers - New entry should be added to previous_answers
''' """
m = CHModuleFactory.create() m = CHModuleFactory.create()
json_in = {'problem_name': '26.0'} json_in = {'problem_name': '26.0'}
json_out = json.loads(m.get_hint(json_in))['contents'] out = m.get_hint(json_in)
self.assertTrue(json_out == ' ') self.assertTrue(out == None)
self.assertTrue(['26.0', [None, None, None]] in m.previous_answers) self.assertTrue(['26.0', [None, None, None]] in m.previous_answers)
def test_gethint_1hint(self): def test_gethint_1hint(self):
''' """
Someone asks for a hint, with exactly one hint in the database. Someone asks for a hint, with exactly one hint in the database.
Output should contain that hint. Output should contain that hint.
''' """
m = CHModuleFactory.create() m = CHModuleFactory.create()
json_in = {'problem_name': '25.0'} json_in = {'problem_name': '25.0'}
json_out = json.loads(m.get_hint(json_in))['contents'] out = m.get_hint(json_in)
self.assertTrue('Really popular hint' in json_out) self.assertTrue(out['best_hint'] == 'Really popular hint')
def test_gethint_manyhints(self): def test_gethint_manyhints(self):
''' """
Someone asks for a hint, with many matching hints in the database. Someone asks for a hint, with many matching hints in the database.
- The top-rated hint should be returned. - The top-rated hint should be returned.
- Two other random hints should be returned. - Two other random hints should be returned.
Currently, the best hint could be returned twice - need to fix this Currently, the best hint could be returned twice - need to fix this
in implementation. in implementation.
''' """
m = CHModuleFactory.create() m = CHModuleFactory.create()
json_in = {'problem_name': '24.0'} json_in = {'problem_name': '24.0'}
json_out = json.loads(m.get_hint(json_in))['contents'] out = m.get_hint(json_in)
print json_out self.assertTrue(out['best_hint'] == 'Best hint')
self.assertTrue('Best hint' in json_out) self.assertTrue('rand_hint_1' in out)
self.assertTrue(json_out.count('hint') == 3) self.assertTrue('rand_hint_2' in out)
def test_getfeedback_0wronganswers(self): def test_getfeedback_0wronganswers(self):
''' """
Someone has gotten the problem correct on the first try. Someone has gotten the problem correct on the first try.
Output should be empty. Output should be empty.
''' """
m = CHModuleFactory.create(previous_answers=[]) m = CHModuleFactory.create(previous_answers=[])
json_in = {'problem_name': '42.5'} json_in = {'problem_name': '42.5'}
json_out = json.loads(m.get_feedback(json_in))['contents'] out = m.get_feedback(json_in)
self.assertTrue(json_out == ' ') self.assertTrue(out == None)
def test_getfeedback_1wronganswer_nohints(self): def test_getfeedback_1wronganswer_nohints(self):
''' """
Someone has gotten the problem correct, with one previous wrong Someone has gotten the problem correct, with one previous wrong
answer. However, we don't actually have hints for this problem. answer. However, we don't actually have hints for this problem.
There should be a dialog to submit a new hint. There should be a dialog to submit a new hint.
''' """
m = CHModuleFactory.create(previous_answers=[['26.0',[None, None, None]]]) m = CHModuleFactory.create(previous_answers=[['26.0',[None, None, None]]])
json_in = {'problem_name': '42.5'} json_in = {'problem_name': '42.5'}
json_out = json.loads(m.get_feedback(json_in))['contents'] out = m.get_feedback(json_in)
self.assertTrue('textarea' in json_out) print out['index_to_answer']
self.assertTrue('Vote' not in json_out) self.assertTrue(out['index_to_hints'][0] == [])
self.assertTrue(out['index_to_answer'][0] == '26.0')
def test_getfeedback_1wronganswer_withhints(self): def test_getfeedback_1wronganswer_withhints(self):
''' """
Same as above, except the user did see hints. There should be Same as above, except the user did see hints. There should be
a voting dialog, with the correct choices, plus a hint submission a voting dialog, with the correct choices, plus a hint submission
dialog. dialog.
''' """
m = CHModuleFactory.create( m = CHModuleFactory.create(
previous_answers=[ previous_answers=[
['24.0', [0, 3, None]]], ['24.0', [0, 3, None]]],
) )
json_in = {'problem_name': '42.5'} json_in = {'problem_name': '42.5'}
json_out = json.loads(m.get_feedback(json_in))['contents'] out = m.get_feedback(json_in)
self.assertTrue('Best hint' in json_out) self.assertTrue(len(out['index_to_hints'][0])==2)
self.assertTrue('Another hint' in json_out)
self.assertTrue('third hint' not in json_out)
self.assertTrue('textarea' in json_out)
def test_vote_nopermission(self): def test_vote_nopermission(self):
''' """
A user tries to vote for a hint, but he has already voted! A user tries to vote for a hint, but he has already voted!
Should not change any vote tallies. Should not change any vote tallies.
''' """
m = CHModuleFactory.create(user_voted=True) m = CHModuleFactory.create(user_voted=True)
json_in = {'answer': 0, 'hint': 1} json_in = {'answer': 0, 'hint': 1}
old_hints = copy.deepcopy(m.hints) old_hints = copy.deepcopy(m.hints)
...@@ -195,21 +195,21 @@ class CrowdsourceHinterTest(unittest.TestCase): ...@@ -195,21 +195,21 @@ class CrowdsourceHinterTest(unittest.TestCase):
def test_vote_withpermission(self): def test_vote_withpermission(self):
''' """
A user votes for a hint. A user votes for a hint.
''' """
m = CHModuleFactory.create() m = CHModuleFactory.create()
json_in = {'answer': 0, 'hint': 3} json_in = {'answer': 0, 'hint': 3}
json_out = json.loads(m.tally_vote(json_in))['contents'] m.tally_vote(json_in)
self.assertTrue(m.hints['24.0']['0'][1] == 40) self.assertTrue(m.hints['24.0']['0'][1] == 40)
self.assertTrue(m.hints['24.0']['3'][1] == 31) self.assertTrue(m.hints['24.0']['3'][1] == 31)
self.assertTrue(m.hints['24.0']['4'][1] == 20) self.assertTrue(m.hints['24.0']['4'][1] == 20)
def test_submithint_nopermission(self): def test_submithint_nopermission(self):
''' """
A user tries to submit a hint, but he has already voted. A user tries to submit a hint, but he has already voted.
''' """
m = CHModuleFactory.create(user_voted=True) m = CHModuleFactory.create(user_voted=True)
json_in = {'answer': 1, 'hint': 'This is a new hint.'} json_in = {'answer': 1, 'hint': 'This is a new hint.'}
print m.user_voted print m.user_voted
...@@ -219,39 +219,37 @@ class CrowdsourceHinterTest(unittest.TestCase): ...@@ -219,39 +219,37 @@ class CrowdsourceHinterTest(unittest.TestCase):
def test_submithint_withpermission_new(self): def test_submithint_withpermission_new(self):
''' """
A user submits a hint to an answer for which no hints A user submits a hint to an answer for which no hints
exist yet. exist yet.
''' """
m = CHModuleFactory.create() m = CHModuleFactory.create()
json_in = {'answer': 1, 'hint': 'This is a new hint.'} json_in = {'answer': 1, 'hint': 'This is a new hint.'}
m.submit_hint(json_in) m.submit_hint(json_in)
# Make a hint request. self.assertTrue('29.0' in m.hints)
json_in = {'problem name': '29.0'}
json_out = json.loads(m.get_hint(json_in))['contents']
self.assertTrue('This is a new hint.' in json_out)
def test_submithint_withpermission_existing(self): def test_submithint_withpermission_existing(self):
''' """
A user submits a hint to an answer that has other hints A user submits a hint to an answer that has other hints
already. already.
''' """
m = CHModuleFactory.create(previous_answers = [['25.0', [1, None, None]]]) m = CHModuleFactory.create(previous_answers = [['25.0', [1, None, None]]])
json_in = {'answer': 0, 'hint': 'This is a new hint.'} json_in = {'answer': 0, 'hint': 'This is a new hint.'}
m.submit_hint(json_in) m.submit_hint(json_in)
# Make a hint request. # Make a hint request.
json_in = {'problem name': '25.0'} json_in = {'problem name': '25.0'}
json_out = json.loads(m.get_hint(json_in))['contents'] out = m.get_hint(json_in)
self.assertTrue('This is a new hint.' in json_out) self.assertTrue((out['best_hint'] == 'This is a new hint.')
or (out['rand_hint_1'] == 'This is a new hint.'))
def test_submithint_moderate(self): def test_submithint_moderate(self):
''' """
A user submits a hint, but moderation is on. The hint should A user submits a hint, but moderation is on. The hint should
show up in the mod_queue, not the public-facing hints show up in the mod_queue, not the public-facing hints
dict. dict.
''' """
m = CHModuleFactory.create(moderate='True') m = CHModuleFactory.create(moderate='True')
json_in = {'answer': 1, 'hint': 'This is a new hint.'} json_in = {'answer': 1, 'hint': 'This is a new hint.'}
m.submit_hint(json_in) m.submit_hint(json_in)
...@@ -259,9 +257,28 @@ class CrowdsourceHinterTest(unittest.TestCase): ...@@ -259,9 +257,28 @@ class CrowdsourceHinterTest(unittest.TestCase):
self.assertTrue('29.0' in m.mod_queue) self.assertTrue('29.0' in m.mod_queue)
def test_template_gethint(self):
"""
Test the templates for get_hint.
"""
m = CHModuleFactory.create()
def fake_get_hint(get):
"""
Creates a rendering dictionary, with which we can test
the templates.
"""
return {'best_hint': 'This is the best hint.',
'rand_hint_1': 'A random hint',
'rand_hint_2': 'Another random hint',
'answer': '42.5'}
m.get_hint = fake_get_hint
json_in = {'problem_name': '42.5'}
out = json.loads(m.handle_ajax('get_hint', json_in))['contents']
self.assertTrue('This is the best hint.' in out)
self.assertTrue('A random hint' in out)
self.assertTrue('Another random hint' in out)
......
## The hinter module passes in a field called ${op}, which determines which
## sub-function to render.
<%def name="get_hint()">
% if best_hint != '':
<h4> Other students who arrvied at the wrong answer of ${answer} recommend the following hints: </h4>
<ul>
<li> ${best_hint} </li>
% endif
% if rand_hint_1 != '':
<li> ${rand_hint_1} </li>
% endif
% if rand_hint_2 != '':
<li> ${rand_hint_2} </li>
% endif
</ul>
</%def>
<%def name="get_feedback()">
<i> Participation in the hinting system is strictly optional, and will not influence
your grade. </i>
<br />
Help us improve our hinting system. Start by picking one of your previous incorrect answers from below:
<br /><br />
<div id="answer-tabs">
<ul>
% for index, answer in index_to_answer.items():
<li><a href="#previous-answer-${index}"> ${answer} </a></li>
% endfor
</ul>
% for index, answer in index_to_answer.items():
<div class = "previous-answer" id="previous-answer-${index}">
% if index in index_to_hints and len(index_to_hints[index]) > 0:
Which hint was most helpful when you got the wrong answer of ${answer}?
<br />
% for hint_text, hint_pk in index_to_hints[index]:
<input class="vote" data-answer="${index}" data-hintno="${hint_pk}" type="button" value="Vote">
${hint_text}
<br />
% endfor
Don't like any of the hints above? You can also submit your own.
% else:
Write a hint for other students who get the wrong answer of ${answer}.
% endif
Try to describe what concepts you misunderstood, or what mistake you made. Please don't
give away the answer.
<textarea cols="50" style="height:100px" class="custom-hint" id="custom-hint-${index}">
What would you say to help someone who got this wrong answer?
(Don't give away the answer, please.)
</textarea>
<input class="submit-hint" data-answer="${index}" type="button" value="submit">
</div>
% endfor
</div>
</%def>
<%def name="show_votes()">
Thank you for voting!
<br />
% for hint, votes in hint_and_votes:
<span style="color:green"> ${votes} votes. </span>
${hint}
<br />
% endfor
</%def>
<%def name="simple_message()">
${message}
</%def>
% if op == "get_hint":
${get_hint()}
% endif
% if op == "get_feedback":
${get_feedback()}
% endif
% if op == "submit_hint":
${simple_message()}
% endif
% if op == "vote":
${show_votes()}
% endif
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment