Commit 578b7ba5 by solashirai

merged with master

parents 67924b23 8f34322b
*.pyc *.pyc
*~ *~
*.egg-info/ *.egg-info/
demo.tar.gz
<section class="about">
<h2>About This Course</h2>
<p>Include your long course description here. The long course description should contain 150-400 words.</p>
<p>This is paragraph 2 of the long course description. Add more paragraphs as needed. Make sure to enclose them in paragraph tags.</p>
</section>
<section class="prerequisites">
<h2>Prerequisites</h2>
<p>Add information about course prerequisites here.</p>
</section>
<section class="course-staff">
<h2>Course Staff</h2>
<article class="teacher">
<div class="teacher-image">
<img src="/static/images/pl-faculty.png" align="left" style="margin:0 20 px 0">
</div>
<h3>Staff Member #1</h3>
<p>Biography of instructor/staff member #1</p>
</article>
<article class="teacher">
<div class="teacher-image">
<img src="/static/images/pl-faculty.png" align="left" style="margin:0 20 px 0">
</div>
<h3>Staff Member #2</h3>
<p>Biography of instructor/staff member #2</p>
</article>
</section>
<section class="faq">
<section class="responses">
<h2>Frequently Asked Questions</h2>
<article class="response">
<h3>What web browser should I use?</h3>
<p>The Open edX platform works best with current versions of Chrome, Firefox or Safari, or with Internet Explorer version 9 and above.</p>
<p>See our <a href="http://edx.readthedocs.org/en/latest/browsers.html">list of supported browsers</a> for the most up-to-date information.</p>
</article>
<article class="response">
<h3>Question #2</h3>
<p>Your answer would be displayed here.</p>
</article>
</section>
</section>
<assets/>
\ No newline at end of file
<?xml version="1.0" ?>
<course advanced_modules="[&quot;crowdsourcehinter&quot;]" course="CSH" display_name="Crowdsourced Hinter Demonstration Course" graded="false" ispublic="false" org="edX" rerandomize="never" show_calculator="false" showanswer="attempted" source_file="null" start="2013-02-05T05:00:00Z" url_name="CSH_Course">
<chapter display_name="Crowdsourced Hinter" url_name="csh">
<sequential display_name="CSH With Settings" url_name="csh_settings_s">
<vertical display_name="CSH With Settings" url_name="csh_settings_v">
<problem url_name="michigan_problem_prehint"/>
<crowdsourcehinter Element="i4x://edX/CSH_Course/problem/sample_problem"
display_name="Unit"
generic_hints="[&quot;Try checking for spelling mistakes.&quot;]"
initial_hints="{&quot;michigann&quot;: &quot;Your answer has too many Ns.&quot;}"
url_name="Unit_0"
xblock-family="xblock.v1"/>
</vertical>
</sequential>
<sequential display_name="CSH Without Settings" url_name="csh_plain_s">
<vertical display_name="CSH Without Settings" url_name="csh_plain_v">
<problem url_name="michigan_problem_nohint"/>
<crowdsourcehinter display_name="Unit"
url_name="Unit_2"
xblock-family="xblock.v1"/>
</vertical>
</sequential>
</chapter>
</course>
<ol class="treeview-handoutsnav">
<li><a href="/static/demoPDF.pdf"> Example handout </a> </li>
</ol>
<ol><li><h2>Welcome!</h2>Hi! Welcome to the edX demonstration course. We built this to help you become more familiar with taking a course on edX prior to your first day of class.
<br><br>
In a live course, this section is where all of the latest course announcements and updates would be. To get started with this demo course, view the
<a href="/course/courseware/d8a6192ade314473a78242dfeedfbf5b/edx_introduction/">courseware page</a> and click &#8220;Example Week 1&#8221; in the left hand navigation.
<br><br>
We tried to make this both fun and informative. We hope you like it. &#8211; The edX Team
</li></ol>
\ No newline at end of file
{
"GRADER": [
{
"drop_count": 1,
"min_count": 3,
"short_label": "Ex",
"type": "Homework",
"weight": 0.75
},
{
"drop_count": 0,
"min_count": 1,
"short_label": "",
"type": "Exam",
"weight": 0.25
}
],
"GRADE_CUTOFFS": {
"Pass": 0.6
}
}
\ No newline at end of file
{
"course/Demo_Course": {
"advanced_modules": [
"crowdsourcehinter"
],
"display_name": "CSH Demonstration Course",
"graded": false,
"rerandomize": "never",
"show_calculator": false,
"showanswer": "attempted",
}
}
<problem display_name="Text Input" markdown="The hinter for this problem is not manually configured.&#10;&#10;&gt;&gt;Which US state has Lansing as its capital?&lt;&lt;&#10;&#10;= Michigan&#10;&#10;&#10;[explanation]&#10;Lansing is the capital of Michigan, although it is not Michigan's largest city, or even the seat of the county in which it resides.&#10;[explanation]&#10;" url_name="Text_Input_6">
<p display_name="Text Input" url_name="Text_Input_7">The hinter for this problem is not manually configured.</p>
<p display_name="Text Input" url_name="Text_Input_8">Which US state has Lansing as its capital?</p>
<stringresponse answer="Michigan" display_name="Text Input" type="ci" url_name="Text_Input_9">
<textline label="Which US state has Lansing as its capital?" size="20" />
</stringresponse>
<solution display_name="Text Input" url_name="Text_Input_10">
<div class="detailed-solution">
<p>Explanation</p>
<p>Lansing is the capital of Michigan, although it is not Michigan's largest city, or even the seat of the county in which it resides.</p>
</div>
</solution>
</problem>
\ No newline at end of file
<problem display_name="Text Input" markdown="This is an example problem with an attached hinter. Specific hints and the Element have been manually set for this hinter.&#10;&#10;The generic hint is &quot;Try checking for spelling mistakes&quot;.&#10;A specific hint is set for the incorrect answer &quot;michigann&quot;.&#10;&#10;&gt;&gt;Which US state has Lansing as its capital?&lt;&lt;&#10;&#10;= Michigan&#10;&#10;&#10;[explanation]&#10;Lansing is the capital of Michigan, although it is not Michigan's largest city, or even the seat of the county in which it resides.&#10;[explanation]&#10;" url_name="Text_Input">
<p display_name="Text Input" url_name="Text_Input_0">This is an example problem with an attached hinter. Specific hints and the Element have been manually set for this hinter.</p>
<p display_name="Text Input" url_name="Text_Input_1">The generic hint is "Try checking for spelling mistakes".</p>
<p display_name="Text Input" url_name="Text_Input_2">A specific hint is set for the incorrect answer "michigann".</p>
<p display_name="Text Input" url_name="Text_Input_3">Which US state has Lansing as its capital?</p>
<stringresponse answer="Michigan" display_name="Text Input" type="ci" url_name="Text_Input_4">
<textline label="Which US state has Lansing as its capital?" size="20" />
</stringresponse>
<solution display_name="Text Input" url_name="Text_Input_5">
<div class="detailed-solution">
<p>Explanation</p>
<p>Lansing is the capital of Michigan, although it is not Michigan's largest city, or even the seat of the county in which it resides.</p>
</div>
</solution>
</problem>
\ No newline at end of file
tar cfvz demo.tar.gz Demo_Course
import ast
import logging import logging
import operator import operator
import pkg_resources import pkg_resources
...@@ -6,65 +5,96 @@ import random ...@@ -6,65 +5,96 @@ import random
import json import json
import copy import copy
import HTMLParser
from xblock.core import XBlock from xblock.core import XBlock
from xblock.fields import Scope, Dict, List, Boolean, String from xblock.fields import Scope, Dict, List, Boolean, String
from xblock.fragment import Fragment from xblock.fragment import Fragment
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
html_parser = HTMLParser.HTMLParser()
class CrowdsourceHinter(XBlock): class CrowdsourceHinter(XBlock):
""" """
This is the Crowdsource Hinter XBlock. This Xblock seeks to provide students with hints This is the Crowdsource Hinter XBlock. This Xblock provides
that specifically address their mistake. Additionally, the hints that this Xblock shows students with hints that specifically address their
are created by the students themselves. This doc string will probably be edited later. mistake. The hints are crowdsourced from the students.
""" """
# Database of hints. hints are stored as such: {"incorrect_answer": {"hint": rating}}. each key (incorrect answer)
# has a corresponding dictionary (in which hints are keys and the hints' ratings are the values). # Database of hints. hints are stored:
# # {"incorrect_answer": {"hint": rating}}.
# Example: {"computerr": {"You misspelled computer, remove the last r.": 5}} # Each key (incorrect answer) has a corresponding dictionary (in
# which hints are keys and the hints' ratings are the values).
# For example:
# {"computerr": {"You misspelled computer, remove the last r.": 5}}
# TODO: We should store upvotes and downvotes independently.
hint_database = Dict(default={}, scope=Scope.user_state_summary) hint_database = Dict(default={}, scope=Scope.user_state_summary)
# Database of initial hints, set by the course instructor. hint_database will receive the hints inputted
# in initial_hints. Initial hints have a default rating of 0. # Database of initial hints, set by the course
# instructor. hint_database will receive the hints inputted in
# initial_hints. Initial hints have a default rating of 0.
# #
# Example: {"Jeorge Washington": "You spelled his first name wrong."} # For example:
# {"Jeorge Washington": "You spelled his first name wrong."}
initial_hints = Dict(default={}, scope=Scope.content) initial_hints = Dict(default={}, scope=Scope.content)
# This is a list of incorrect answer submissions made by the student. this list is mostly used for
# when the student starts rating hints, to find which incorrect answer's hint a student voted on. # This is a list of incorrect answer submissions made by the
# student. this list is used when the student starts rating hints,
# to find which incorrect answer's hint a student voted on.
# #
# Example: ["personal computer", "PC", "computerr"] # For example:
# ["personal computer", "PC", "computerr"]
incorrect_answers = List([], scope=Scope.user_state) incorrect_answers = List([], scope=Scope.user_state)
# A dictionary of generic_hints. default hints will be shown to students when there are no matches with the
# student's incorrect answer within the hint_database dictionary (i.e. no students have made hints for the # A dictionary of generic_hints. default hints will be shown to
# particular incorrect answer) # students when there are no matches with the student's incorrect
# answer within the hint_database dictionary (i.e. no students
# have made hints for the particular incorrect answer)
# #
# Example: ["Make sure to check your answer for simple mistakes like typos!"] # For example:
# ["Make sure to check your answer for simple mistakes like typos!"]
generic_hints = List(default=[], scope=Scope.content) generic_hints = List(default=[], scope=Scope.content)
# List of which hints have been shown to the student
# this list is used to prevent the same hint from showing up to a student (if they submit the same incorrect answers # This is a list hints have been shown to the student. We'd like
# multiple times) # to avoid showing the same hint from showing up to a student (if
# they submit the same incorrect answers multiple times), and we
# may like to know this after the students submits a correct
# answer (if we want to e.g. vet hints)
# #
# Example: ["You misspelled computer, remove the last r."] # For example:
# ["You misspelled computer, remove the last r."]
used = List([], scope=Scope.user_state) used = List([], scope=Scope.user_state)
# This is a dictionary of hints that have been reported. the values represent the incorrect answer submission, and the
# keys are the hints the corresponding hints. hints with identical text for differing answers will all not show up for the # This is a dictionary of hints that have been flagged or reported
# student. # as malicious (spam, profanity, give-aways, etc.). The values
# represent incorrect answer submissions. The keys are the hints
# the corresponding hints. hints with identical text for differing
# answers will all not show up for the student.
# #
# Example: {"desk": "You're completely wrong, the answer is supposed to be computer."} # For example:
# {"desk": "You're completely wrong, the answer is supposed to be computer."}
# TODO: It's not clear how this data structure will manage multiple
# reported hints for the same wrong answer.
reported_hints = Dict(default={}, scope=Scope.user_state_summary) reported_hints = Dict(default={}, scope=Scope.user_state_summary)
# This String represents the xblock element for which the hinter is running. It is necessary to manually
# set this value in the XML file under the format "hinting_element": "i4x://edX/DemoX/problem/Text_Input" . # This String represents the xblock element for which the hinter
# Setting the element in the XML file is critical for the hinter to work. # is delivering hints. It is necessary to manually set this value
# in the XML file under the format "hinting_element":
# "i4x://edX/DemoX/problem/Text_Input"
# #
# TODO: probably should change the name from Element (problem_element? hinting_element?). Trying to # TODO: probably should change the name from Element
# just change the name didn't seem to operate properly, check carefully what is changed # (problem_element? hinting_element?). Trying to just change the
# name didn't seem to operate properly, check carefully what is
# changed
Element = String(default="", scope=Scope.content) Element = String(default="", scope=Scope.content)
def studio_view(self, context=None): def studio_view(self, context=None):
""" """
This function defines a view for editing the XBlock when embedding it in a course. It will allow This function defines a view for editing the XBlock when embedding
one to define, for example, which problem the hinter is for. It is unfinished and does not currently it in a course. It will allow one to define, for example,
work. which problem the hinter is for.
It is currently incomplete -- we still need to finish building the
authoring view.
""" """
html = self.resource_string("static/html/crowdsourcehinterstudio.html") html = self.resource_string("static/html/crowdsourcehinterstudio.html")
frag = Fragment(html) frag = Fragment(html)
...@@ -77,31 +107,25 @@ class CrowdsourceHinter(XBlock): ...@@ -77,31 +107,25 @@ class CrowdsourceHinter(XBlock):
@XBlock.json_handler @XBlock.json_handler
def set_initial_settings(self, data, suffix=''): def set_initial_settings(self, data, suffix=''):
""" """
Set intial hints, generic hints, and problem element from the studio view. Set intial hints, generic hints, and problem element from the
studio view.
The Studio view is not yet complete.
""" """
initial = ast.literal_eval(str(data['initial_hints'])) initial_hints = json.loads(data['initial_hints'])
generic = ast.literal_eval(str(data['generic_hints'])) generic_hints = json.loads(data['generic_hints'])
if type(generic) is list and type(initial) is dict:
self.initial_hints = initial
self.generic_hints = generic
self.Element = str(data['element'])
return {'success': True}
return {'success': False}
def resource_string(self, path): if not isinstance(generic_hints, list):
""" return {'success': False,
This function is used to get the path of static resources. 'error': 'Generic hints should be a list.'}
""" if not isinstance(initial_hints, dict):
data = pkg_resources.resource_string(__name__, path) return {'success': False,
return data.decode("utf8") 'error' : 'Initial hints should be a dict.'}
def get_user_is_staff(self): self.initial_hints = initial_hints
""" self.generic_hints = generic_hints
Return self.xmodule_runtime.user_is_staff if len(str(data['element'])) > 1:
This is not a supported part of the XBlocks API. User data is still self.Element = str(data['element'])
being defined. However, It's the only way to get the data right now. return {'success': True}
"""
return self.xmodule_runtime.user_is_staff
def student_view(self, context=None): def student_view(self, context=None):
""" """
...@@ -113,14 +137,28 @@ class CrowdsourceHinter(XBlock): ...@@ -113,14 +137,28 @@ class CrowdsourceHinter(XBlock):
frag.add_javascript_url('//cdnjs.cloudflare.com/ajax/libs/mustache.js/0.8.1/mustache.min.js') frag.add_javascript_url('//cdnjs.cloudflare.com/ajax/libs/mustache.js/0.8.1/mustache.min.js')
frag.add_css(self.resource_string("static/css/crowdsourcehinter.css")) frag.add_css(self.resource_string("static/css/crowdsourcehinter.css"))
frag.add_javascript(self.resource_string("static/js/src/crowdsourcehinter.js")) frag.add_javascript(self.resource_string("static/js/src/crowdsourcehinter.js"))
frag.initialize_js('CrowdsourceHinter', {'hinting_element': self.Element, 'isStaff': self.xmodule_runtime.user_is_staff}) frag.initialize_js('CrowdsourceHinter', {'hinting_element': self.Element, 'isStaff': get_user_is_staff})
return frag return frag
def extract_student_answers(self, answers):
"""
We find out what the student submitted by listening to a
client-side event. This event is a little bit messy. This
function cleans up the event into a dictionary mapping
input IDs to answers submitted.
"""
# First, we split this into the submission
answers = [a.split('=') for a in answers.split("&")]
# Next, we decode the HTML escapes
answers = [(a[0], HTMLparser.unescape(a[1])) for a in answers]
return dict(answers)
@XBlock.json_handler @XBlock.json_handler
def get_hint(self, data, suffix=''): def get_hint(self, data, suffix=''):
""" """
Returns hints to students. Hints with the highest rating are shown to students unless the student has already Returns hints to students. Hints with the highest rating are shown
submitted the same incorrect answer previously. to students unless the student has already submitted the same
incorrect answer previously.
Args: Args:
data['submittedanswer']: The string of text that the student submits for a problem. data['submittedanswer']: The string of text that the student submits for a problem.
returns: returns:
...@@ -129,80 +167,78 @@ class CrowdsourceHinter(XBlock): ...@@ -129,80 +167,78 @@ class CrowdsourceHinter(XBlock):
or 'Sorry, there are no hints for this answer.' if no hints exist or 'Sorry, there are no hints for this answer.' if no hints exist
'StudentAnswer': the student's incorrect answer 'StudentAnswer': the student's incorrect answer
""" """
# populate hint_database with hints from initial_hints if there are no hints in hint_database. # Populate hint_database with hints from initial_hints if
# this probably will occur only on the very first run of a unit containing this block. # there are no hints in hint_database. this probably will
for answers in self.initial_hints: # occur only on the very first run of a unit containing this
if answers not in self.hint_database: # block.
self.hint_database[answers] = {} if not self.hint_database:
if self.initial_hints[answers] not in self.hint_database[answers]: self.hints_database = self.initial_hints
self.hint_database[answers].update({self.initial_hints[answers]: 0})
answer = str(data["submittedanswer"]) answer = self.extract_student_answers(data["submittedanswer"])
# put the student's answer to lower case so that differences in capitalization don't make
# different groups of hints. this is sloppy and the execution should probably be changed. # HACK: For now, we assume just one submission, a string, and
answer = answer.lower() # case insensitive
found_equal_sign = 0
remaining_hints = int(0)
best_hint = ""
# the string returned by the event problem_graded is very messy and is different
# for each problem, but after all of the numbers/letters there is an equal sign, after which the
# student's input is shown. I use the function below to remove everything before the first equal
# sign and only take the student's actual input.
# #
# TODO: figure out better way to directly get text of student's answer # TODO: We should replace this with a generic canonicalization
if "=" in answer: # function
if found_equal_sign == 0: answer = answer.values()[0].lower()
found_equal_sign = 1
eqplace = answer.index("=") + 1 # Put the student's answer to lower case so that differences
answer = answer[eqplace:] # in capitalization don't make different groups of
remaining_hints = str(self.find_hints(answer)) # hints. TODO: We should replace this with a .
if remaining_hints != str(0): remaining_hints = 0 # TODO: This is confused
for hint in self.hint_database[str(answer)]: best_hint = "" # TODO: What is this?
remaining_hints = self.find_hints(answer)
if remaining_hints != 0:
for hint in self.hint_database[answer]:
if hint not in self.reported_hints.keys(): if hint not in self.reported_hints.keys():
# if best_hint hasn't been set yet or the rating of hints is greater than the rating of best_hint # if best_hint hasn't been set yet or the rating of hints is greater than the rating of best_hint
if best_hint == "" or self.hint_database[str(answer)][hint] > self.hint_database[str(answer)][str(best_hint)]: if best_hint == "" or self.hint_database[answer][hint] > self.hint_database[answer][best_hint]:
best_hint = hint best_hint = hint
self.used.append(best_hint) self.used.append(best_hint)
return {'BestHint': best_hint, "StudentAnswer": answer} return {'BestHint': best_hint, "StudentAnswer": answer}
# find generic hints for the student if no specific hints exist # find generic hints for the student if no specific hints exist
if len(self.generic_hints) != 0: if self.generic_hints:
generic_hint = random.choice(self.generic_hints) generic_hint = random.choice(self.generic_hints)
self.used.append(generic_hint) self.used.append(generic_hint)
return {'BestHint': generic_hint, "StudentAnswer": answer} return {'BestHint': generic_hint, "StudentAnswer": answer}
else: else:
# if there are no hints in either the database or generic hints # if there are no hints in either the database or generic hints
self.used.append(str("There are no hints for" + " " + answer)) self.used.append("There are no hints for" + " " + answer)
return {'BestHint': "Sorry, there are no hints for this answer.", "StudentAnswer": answer} return {'BestHint': "Sorry, there are no hints for this answer.", "StudentAnswer": answer}
def find_hints(self, answer): def find_hints(self, answer):
""" """
This function is used to check that an incorrect answer has available hints to show. This function is used to check that an incorrect answer has
It will also add the incorrect answer test to self.incorrect_answers. available hints to show. It will also add the incorrect
answer test to self.incorrect_answers.
Args: Args:
answer: This is equal to answer from get_hint, the answer the student submitted answer: This is equal to answer from get_hint, the answer the student submitted
Returns 0 if no hints to show exist Returns 0 if no hints to show exist
""" """
isreported = [] isreported = []
self.incorrect_answers.append(str(answer)) self.incorrect_answers.append(answer)
if str(answer) not in self.hint_database: if answer not in self.hint_database:
# add incorrect answer to hint_database if no precedent exists # add incorrect answer to hint_database if no precedent exists
self.hint_database[str(answer)] = {} self.hint_database[answer] = {}
return str(0) return 0
for hint_keys in self.hint_database[str(answer)]: for hint_key in self.hint_database[answer]:
for reported_keys in self.reported_hints: for reported_keys in self.reported_hints:
if hint_keys == reported_keys: if hint_key in self.reported_hints:
isreported.append(hint_keys) isreported.append(hint_key)
if (len(self.hint_database[str(answer)]) - len(isreported)) > 0: if (len(self.hint_database[answer]) - len(isreported)) > 0:
return str(1) return 1
else: else:
return str(0) return 0
@XBlock.json_handler @XBlock.json_handler
def get_used_hint_answer_data(self, data, suffix=''): def get_used_hint_answer_data(self, data, suffix=''):
""" """
This function helps to facilitate student rating of hints and contribution of new hints. This function helps to facilitate student rating of hints and
Specifically this function is used to send necessary data to JS about incorrect answer contribution of new hints. Specifically this function is used
submissions and hints. It also will return hints that have been reported, although this to send necessary data to JS about incorrect answer
is only for Staff. submissions and hints. It also will return hints that have
been reported, although this is only for Staff.
Returns: Returns:
used_hint_answer_text: This dicitonary contains reported hints/answers (if the user is staff) and the used_hint_answer_text: This dicitonary contains reported hints/answers (if the user is staff) and the
first hint/answer pair that the student submitted for a problem. first hint/answer pair that the student submitted for a problem.
...@@ -214,24 +250,18 @@ class CrowdsourceHinter(XBlock): ...@@ -214,24 +250,18 @@ class CrowdsourceHinter(XBlock):
used_hint_answer_text = {} used_hint_answer_text = {}
if self.get_user_is_staff(): if self.get_user_is_staff():
for key in self.reported_hints: for key in self.reported_hints:
used_hint_answer_text[key] = str("Reported") used_hint_answer_text[key] = "Reported"
if len(self.incorrect_answers) == 0: if len(self.incorrect_answers) == 0:
return used_hint_answer_text return used_hint_answer_text
else: else:
for index in range(0, len(self.used)): for index in range(0, len(self.used)):
# each index is a hint that was used, in order of usage # each index is a hint that was used, in order of usage
if str(self.used[index]) in self.hint_database[self.incorrect_answers[index]]: if self.used[index] in self.hint_database[self.incorrect_answers[index]]:
# add new key (hint) to used_hint_answer_text with a value (incorrect answer) # add new key (hint) to used_hint_answer_text with a value (incorrect answer)
used_hint_answer_text[str(self.used[index])] = str(self.incorrect_answers[index]) used_hint_answer_text[self.used[index]] = self.incorrect_answers[index]
self.incorrect_answers = []
self.used = []
return used_hint_answer_text
else: else:
# if the student's answer had no hints (or all the hints were reported and unavailable) return None # if the student's answer had no hints (or all the hints were reported and unavailable) return None
used_hint_answer_text[None] = str(self.incorrect_answers[index]) used_hint_answer_text[None] = self.incorrect_answers[index]
self.incorrect_answers = []
self.used = []
return used_hint_answer_text
self.incorrect_answers = [] self.incorrect_answers = []
self.used = [] self.used = []
return used_hint_answer_text return used_hint_answer_text
...@@ -240,7 +270,8 @@ class CrowdsourceHinter(XBlock): ...@@ -240,7 +270,8 @@ class CrowdsourceHinter(XBlock):
def rate_hint(self, data, suffix=''): def rate_hint(self, data, suffix=''):
""" """
Used to facilitate hint rating by students. Used to facilitate hint rating by students.
Hint ratings in hint_database are updated and the resulting hint rating (or reported status) is returned to JS. Hint ratings in hint_database are updated and the resulting
hint rating (or reported status) is returned to JS.
Args: Args:
data['student_answer']: The incorrect answer that corresponds to the hint that is being rated data['student_answer']: The incorrect answer that corresponds to the hint that is being rated
data['hint']: The hint that is being rated data['hint']: The hint that is being rated
...@@ -256,21 +287,21 @@ class CrowdsourceHinter(XBlock): ...@@ -256,21 +287,21 @@ class CrowdsourceHinter(XBlock):
return {"rating": None, 'hint': data_hint} return {"rating": None, 'hint': data_hint}
if data['student_rating'] == 'unreport': if data['student_rating'] == 'unreport':
for reported_hints in self.reported_hints: for reported_hints in self.reported_hints:
if reported_hints == data_hint: if data_hint in self.reported_hints:
self.reported_hints.pop(data_hint, None) self.reported_hints.pop(data_hint, None)
return {'rating': 'unreported'} return {'rating': 'unreported'}
if data['student_rating'] == 'remove': if data['student_rating'] == 'remove':
for reported_hints in self.reported_hints: for reported_hints in self.reported_hints:
if data_hint == reported_hints: if data_hint in self.reported_hints:
self.hint_database[self.reported_hints[data_hint]].pop(data_hint, None) self.hint_database[self.reported_hints[data_hint]].pop(data_hint, None)
self.reported_hints.pop(data_hint, None) self.reported_hints.pop(data_hint, None)
return {'rating': 'removed'} return {'rating': 'removed'}
if data['student_rating'] == 'report': if data['student_rating'] == 'report':
# add hint to Reported dictionary # add hint to Reported dictionary
self.reported_hints[str(data_hint)] = answer_data self.reported_hints[data_hint] = answer_data
return {"rating": 'reported', 'hint': data_hint} return {"rating": 'reported', 'hint': data_hint}
rating = self.change_rating(data_hint, data_rating, answer_data) rating = self.change_rating(data_hint, data_rating, answer_data)
return {"rating": str(rating), 'hint': data_hint} return {"rating": rating, 'hint': data_hint}
def change_rating(self, data_hint, data_rating, answer_data): def change_rating(self, data_hint, data_rating, answer_data):
""" """
...@@ -288,11 +319,11 @@ class CrowdsourceHinter(XBlock): ...@@ -288,11 +319,11 @@ class CrowdsourceHinter(XBlock):
if any(data_hint in generic_hints for generic_hints in self.generic_hints): if any(data_hint in generic_hints for generic_hints in self.generic_hints):
return return
if data_rating == 'upvote': if data_rating == 'upvote':
self.hint_database[str(answer_data)][str(data_hint)] += 1 delta_rating = 1
return self.hint_database[str(answer_data)][str(data_hint)]
else: else:
self.hint_database[str(answer_data)][str(data_hint)] -= 1 delta_rating = -1
return self.hint_database[str(answer_data)][str(data_hint)] self.hint_database[answer_data][data_hint] += delta_rating
return self.hint_database[answer_data)][data_hint]
@XBlock.json_handler @XBlock.json_handler
def add_new_hint(self, data, suffix=''): def add_new_hint(self, data, suffix=''):
...@@ -304,15 +335,15 @@ class CrowdsourceHinter(XBlock): ...@@ -304,15 +335,15 @@ class CrowdsourceHinter(XBlock):
""" """
submission = data['submission'] submission = data['submission']
answer = data['answer'] answer = data['answer']
if str(submission) not in self.hint_database[str(answer)]: if submission not in self.hint_database[answer]:
self.hint_database[str(answer)].update({submission: 0}) self.hint_database[answer].update({submission: 0})
return return
else: else:
# if the hint exists already, simply upvote the previously entered hint # if the hint exists already, simply upvote the previously entered hint
if str(submission) in self.generic_hints: if submission in self.generic_hints:
return return
else: else:
self.hint_database[str(answer)][str(submission)] += 1 self.hint_database[answer][submission] += 1
return return
@XBlock.json_handler @XBlock.json_handler
...@@ -325,17 +356,17 @@ class CrowdsourceHinter(XBlock): ...@@ -325,17 +356,17 @@ class CrowdsourceHinter(XBlock):
@staticmethod @staticmethod
def workbench_scenarios(): def workbench_scenarios():
"""A canned scenario for display in the workbench.""" """
A canned scenario for display in the workbench.
"""
return [ return [
("CrowdsourceHinter", ("CrowdsourceHinter",
""" """
<verticaldemo> <verticaldemo>
<crowdsourcehinter> <crowdsourcehinter>
{"generic_hints": "Make sure to check for basic mistakes like typos", "initial_hints": {"michiganp": "remove the p at the end.", "michigann": "too many Ns on there."}, "hinting_element": "i4x://edX/DemoX/problem/Text_Input"} {"generic_hints": "Make sure to check for basic mistakes like typos", "initial_hints": {"michiganp": "remove the p at the end.", "michigann": "too many Ns on there."}, "hinting_element": "i4x://edX/DemoX/problem/Text_Input"}
</crowdsourcehinter> </crowdsourcehinter>
</verticaldemo> </verticaldemo>""")
"""
)
] ]
@classmethod @classmethod
...@@ -344,9 +375,25 @@ class CrowdsourceHinter(XBlock): ...@@ -344,9 +375,25 @@ class CrowdsourceHinter(XBlock):
A minimal working test for parse_xml A minimal working test for parse_xml
""" """
block = runtime.construct_xblock_from_class(cls, keys) block = runtime.construct_xblock_from_class(cls, keys)
xmlText = ast.literal_eval(str(node.text)) xmlText = json.loads(node.text)
if xmlText: if xmlText:
block.generic_hints.append(str(xmlText["generic_hints"])) block.generic_hints.append(str(xmlText["generic_hints"]))
block.initial_hints = copy.copy(xmlText["initial_hints"]) block.initial_hints = copy.copy(xmlText["initial_hints"])
block.Element = str(xmlText["hinting_element"]) block.Element = str(xmlText["hinting_element"])
return block return block
# Generic functions/workarounds for XBlock API limitations and incompletions.
def resource_string(self, path):
"""
This function is used to get the path of static resources.
"""
data = pkg_resources.resource_string(__name__, path)
return data.decode("utf8")
def get_user_is_staff(self):
"""
Return self.xmodule_runtime.user_is_staff
This is not a supported part of the XBlocks API. User data is still
being defined. However, it's the only way to get the data right now.
"""
return self.xmodule_runtime.user_is_staff
This static directory is for files that should be included in your kit as plain
static files.
You can ask the runtime for a URL that will retrieve these files with:
url = self.runtime.local_resource_url(self, "static/js/lib.js")
The default implementation is very strict though, and will not serve files from
the static directory. It will serve files from a directory named "public".
Create a directory alongside this one named "public", and put files there.
Then you can get a url with code like this:
url = self.runtime.local_resource_url(self, "public/js/lib.js")
The sample code includes a function you can use to read the content of files
in the static directory, like this:
frag.add_javascript(self.resource_string("static/js/my_block.js"))
...@@ -56,10 +56,6 @@ ...@@ -56,10 +56,6 @@
margin-left: 10px; margin-left: 10px;
} }
div[data-rate="report"]{
font-weight: bold;
}
.csh_hint_text[rating="upvote"]{ .csh_hint_text[rating="upvote"]{
color: green; color: green;
} }
......
...@@ -31,7 +31,7 @@ function CrowdsourceHinter(runtime, element, data){ ...@@ -31,7 +31,7 @@ function CrowdsourceHinter(runtime, element, data){
$.ajax({ $.ajax({
type: "POST", type: "POST",
url: runtime.handlerUrl(element, 'get_hint'), url: runtime.handlerUrl(element, 'get_hint'),
data: JSON.stringify({"submittedanswer": unescape(problemGradedEvent[0])}), data: JSON.stringify({"submittedanswer": problemGradedEvent[0]}),
success: showHint success: showHint
}); });
} }
......
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