Commit 17c710af by cahrens

Merge branch 'master' into feature/christina/metadata-ui

Conflicts:
	common/lib/xmodule/xmodule/combined_open_ended_module.py
	common/lib/xmodule/xmodule/peer_grading_module.py
	common/lib/xmodule/xmodule/templates/combinedopenended/default.yaml
parents 599a215a 256a3677
<%include file="metadata-edit.html" />
<section class="combinedopenended-editor editor">
<div class="row">
%if enable_markdown:
<div class="editor-bar">
<ul class="format-buttons">
<li><a href="#" class="prompt-button" data-tooltip="Prompt"><span
class="combinedopenended-editor-icon icon-quote-left"></span></a></li>
<li><a href="#" class="rubric-button" data-tooltip="Rubric"><span
class="combinedopenended-editor-icon icon-table"></span></a></li>
<li><a href="#" class="tasks-button" data-tooltip="Tasks"><span
class="combinedopenended-editor-icon icon-sitemap"></span></a></li>
</ul>
<ul class="editor-tabs">
<li><a href="#" class="xml-tab advanced-toggle" data-tab="xml">Advanced Editor</a></li>
<li><a href="#" class="cheatsheet-toggle" data-tooltip="Toggle Cheatsheet">?</a></li>
</ul>
</div>
<textarea class="markdown-box">${markdown | h}</textarea>
%endif
<textarea class="xml-box" rows="8" cols="40">${data | h}</textarea>
</div>
</section>
<script type="text/template" id="open-ended-template">
<openended %min_max_string%>
<openendedparam>
<initial_display>Enter essay here.</initial_display>
<answer_display>This is the answer.</answer_display>
<grader_payload>{"grader_settings" : "%grading_config%", "problem_id" : "6.002x/Welcome/OETest"}</grader_payload>
</openendedparam>
</openended>
</script>
<script type="text/template" id="simple-editor-open-ended-cheatsheet">
<article class="simple-editor-open-ended-cheatsheet">
<div class="cheatsheet-wrapper">
<div class="row">
<h6>Prompt</h6>
<div class="col prompt">
</div>
<div class="col">
<pre><code>
[prompt]
Why is the sky blue?
[prompt]
</code></pre>
</div>
<div class="col">
<p>The student will respond to the prompt. The prompt can contain any html tags, such as paragraph tags and header tags.</p>
</div>
</div>
<div class="row">
<h6>Rubric</h6>
<div class="col sample rubric"><!DOCTYPE html>
</div>
<div class="col">
<pre><code>
[rubric]
+ Color Identification
- Incorrect
- Correct
+ Grammar
- Poor
- Acceptable
- Superb
[rubric]
</code></pre>
</div>
<div class="col">
<p>The rubric is used for feedback and self-assessment. The rubric can have as many categories (+) and options (-) as desired. </p>
<p>The total score for the problem will be the sum of all the points possible on the rubric. The options will be numbered sequentially from zero in each category, so each category will be worth as many points as its number of options minus one. </p>
</div>
</div>
<div class="row">
<h6>Tasks</h6>
<div class="col sample tasks">
</div>
<div class="col">
<pre><code>
[tasks]
(Self), ({1-3}AI), ({2-3}Peer)
[tasks]
</code></pre>
</div>
<div class="col">
<p>The tasks define what feedback the student will get from the problem.</p>
<p>Each task is defined with parentheses around it. Brackets (ie {2-3} above), specify the minimum and maximum score needed to attempt the given task.</p>
<p>In the example above, the student will first be asked to self-assess. If they give themselves greater than or equal to a 1/3 and less than or equal to a 3/3 on the problem, then they will be moved to AI assessment. If they score themselves a 2/3 or 3/3 on AI assessment, they will move to peer assessment.</p>
<p>Students will be given feedback from each task, and their final score for a given attempt of the problem will be their score last task that is completed.</p>
</div>
</div>
</div>
</article>
</script>
...@@ -5,7 +5,7 @@ from pkg_resources import resource_string ...@@ -5,7 +5,7 @@ from pkg_resources import resource_string
from xmodule.raw_module import RawDescriptor from xmodule.raw_module import RawDescriptor
from .x_module import XModule from .x_module import XModule
from xblock.core import Scope, String, Integer, Boolean, List from xblock.core import Integer, Scope, String, Boolean, List
from xmodule.open_ended_grading_classes.combined_open_ended_modulev1 import CombinedOpenEndedV1Module, CombinedOpenEndedV1Descriptor from xmodule.open_ended_grading_classes.combined_open_ended_modulev1 import CombinedOpenEndedV1Module, CombinedOpenEndedV1Descriptor
from collections import namedtuple from collections import namedtuple
from .fields import Date, StringyFloat, StringyInteger, StringyBoolean from .fields import Date, StringyFloat, StringyInteger, StringyBoolean
...@@ -49,7 +49,7 @@ class VersionInteger(Integer): ...@@ -49,7 +49,7 @@ class VersionInteger(Integer):
class CombinedOpenEndedFields(object): class CombinedOpenEndedFields(object):
display_name = String(help="Display name for this module", default="Open Ended Grading", scope=Scope.settings) display_name = String(help="Display name for this module", default="Open Ended Grading", scope=Scope.settings)
current_task_number = Integer(help="Current task that the student is on.", default=0, scope=Scope.user_state) current_task_number = StringyInteger(help="Current task that the student is on.", default=0, scope=Scope.user_state)
task_states = List(help="List of state dictionaries of each task within this module.", scope=Scope.user_state) task_states = List(help="List of state dictionaries of each task within this module.", scope=Scope.user_state)
state = String(help="Which step within the current task that the student is on.", default="initial", state = String(help="Which step within the current task that the student is on.", default="initial",
scope=Scope.user_state) scope=Scope.user_state)
...@@ -57,6 +57,7 @@ class CombinedOpenEndedFields(object): ...@@ -57,6 +57,7 @@ class CombinedOpenEndedFields(object):
scope=Scope.user_state) scope=Scope.user_state)
ready_to_reset = StringyBoolean(help="If the problem is ready to be reset or not.", default=False, ready_to_reset = StringyBoolean(help="If the problem is ready to be reset or not.", default=False,
scope=Scope.user_state) scope=Scope.user_state)
// do merge
attempts = StringyInteger(display_name="Maximum Attempts", attempts = StringyInteger(display_name="Maximum Attempts",
help="The number of times the student can try to answer this problem.", default=1, help="The number of times the student can try to answer this problem.", default=1,
scope=Scope.settings, values = {"min" : 1 }) scope=Scope.settings, values = {"min" : 1 })
...@@ -67,6 +68,13 @@ class CombinedOpenEndedFields(object): ...@@ -67,6 +68,13 @@ class CombinedOpenEndedFields(object):
# TODO: passing of text failed with "won't". Need to make our code more robust. # TODO: passing of text failed with "won't". Need to make our code more robust.
help="If False, submissions with poor spelling, short length, or poor grammar will not be peer reviewed.", help="If False, submissions with poor spelling, short length, or poor grammar will not be peer reviewed.",
default=False, scope=Scope.settings) default=False, scope=Scope.settings)
attempts = StringyInteger(help="Maximum number of attempts that a student is allowed.", default=1, scope=Scope.settings)
is_graded = StringyBoolean(help="Whether or not the problem is graded.", default=False, scope=Scope.settings)
accept_file_upload = StringyBoolean(help="Whether or not the problem accepts file uploads.", default=False,
scope=Scope.settings)
skip_spelling_checks = StringyBoolean(help="Whether or not to skip initial spelling checks.", default=True,
scope=Scope.settings)
due = Date(help="Date that this problem is due by", default=None, scope=Scope.settings) due = Date(help="Date that this problem is due by", default=None, scope=Scope.settings)
graceperiod = String(help="Amount of time after the due date that submissions will be accepted", default=None, graceperiod = String(help="Amount of time after the due date that submissions will be accepted", default=None,
scope=Scope.settings) scope=Scope.settings)
...@@ -75,6 +83,7 @@ class CombinedOpenEndedFields(object): ...@@ -75,6 +83,7 @@ class CombinedOpenEndedFields(object):
weight = StringyFloat(display_name="Problem Weight", weight = StringyFloat(display_name="Problem Weight",
help="The number of points the problem is worth. By default, each problem is worth one point.", help="The number of points the problem is worth. By default, each problem is worth one point.",
scope=Scope.settings, values = {"min" : 0 , "step": ".1"}) scope=Scope.settings, values = {"min" : 0 , "step": ".1"})
markdown = String(help="Markdown source of this module", scope=Scope.settings)
class CombinedOpenEndedModule(CombinedOpenEndedFields, XModule): class CombinedOpenEndedModule(CombinedOpenEndedFields, XModule):
...@@ -219,11 +228,36 @@ class CombinedOpenEndedDescriptor(CombinedOpenEndedFields, RawDescriptor): ...@@ -219,11 +228,36 @@ class CombinedOpenEndedDescriptor(CombinedOpenEndedFields, RawDescriptor):
""" """
Module for adding combined open ended questions Module for adding combined open ended questions
""" """
mako_template = "widgets/raw-edit.html" mako_template = "widgets/open-ended-edit.html"
module_class = CombinedOpenEndedModule module_class = CombinedOpenEndedModule
filename_extension = "xml"
stores_state = True stores_state = True
has_score = True has_score = True
always_recalculate_grades = True always_recalculate_grades = True
template_dir_name = "combinedopenended" template_dir_name = "combinedopenended"
#Specify whether or not to pass in S3 interface
needs_s3_interface = True
#Specify whether or not to pass in open ended interface
needs_open_ended_interface = True
metadata_attributes = RawDescriptor.metadata_attributes
js = {'coffee': [resource_string(__name__, 'js/src/combinedopenended/edit.coffee')]}
js_module_name = "OpenEndedMarkdownEditingDescriptor"
css = {'scss': [resource_string(__name__, 'css/editor/edit.scss'), resource_string(__name__, 'css/combinedopenended/edit.scss')]}
def get_context(self):
_context = RawDescriptor.get_context(self)
_context.update({'markdown': self.markdown,
'enable_markdown': self.markdown is not None})
return _context
@property
def non_editable_metadata_fields(self):
non_editable_fields = super(CombinedOpenEndedDescriptor, self).non_editable_metadata_fields
non_editable_fields.extend([CombinedOpenEndedDescriptor.due, CombinedOpenEndedDescriptor.graceperiod,
CombinedOpenEndedDescriptor.markdown])
return non_editable_fields
.editor-bar {
.editor-tabs {
.advanced-toggle {
@include white-button;
height: auto;
margin-top: -1px;
padding: 3px 9px;
font-size: 12px;
&.current {
border: 1px solid $lightGrey !important;
border-radius: 3px !important;
background: $lightGrey !important;
color: $darkGrey !important;
pointer-events: none;
cursor: none;
&:hover {
box-shadow: 0 0 0 0 !important;
}
}
}
.cheatsheet-toggle {
width: 21px;
height: 21px;
padding: 0;
margin: 0 5px 0 15px;
border-radius: 22px;
border: 1px solid #a5aaaf;
background: #e5ecf3;
font-size: 13px;
font-weight: 700;
color: #565d64;
text-align: center;
}
}
}
.simple-editor-open-ended-cheatsheet {
position: absolute;
top: 0;
left: 100%;
width: 0;
border-radius: 0 3px 3px 0;
@include linear-gradient(left, rgba(0, 0, 0, .1), rgba(0, 0, 0, 0) 4px);
background-color: #fff;
overflow: hidden;
@include transition(width .3s);
&.shown {
width: 300px;
height: 100%;
overflow-y: scroll;
}
.cheatsheet-wrapper {
width: 240px;
padding: 20px 30px;
}
h6 {
margin-bottom: 7px;
font-size: 15px;
font-weight: 700;
}
.row {
@include clearfix;
padding-bottom: 5px !important;
margin-bottom: 10px !important;
border-bottom: 1px solid #ddd !important;
&:last-child {
border-bottom: none !important;
margin-bottom: 0 !important;
}
}
.col {
float: left;
&.sample {
width: 60px;
margin-right: 30px;
}
}
pre {
font-size: 12px;
line-height: 18px;
}
code {
padding: 0;
background: none;
}
}
.combinedopenended-editor-icon {
display: inline-block;
vertical-align: middle;
color: #333;
}
<section class="combinedopenended-editor editor">
<div class="row">
<textarea class="markdown-box">markdown</textarea>
<textarea class="xml-box" rows="8" cols="40">xml</textarea>
</div>
</section>
<section class="combinedopenended-editor editor">
<div class="row">
<textarea class="xml-box" rows="8" cols="40">xml only</textarea>
</div>
</section>
describe 'OpenEndedMarkdownEditingDescriptor', ->
describe 'save stores the correct data', ->
it 'saves markdown from markdown editor', ->
loadFixtures 'combinedopenended-with-markdown.html'
@descriptor = new OpenEndedMarkdownEditingDescriptor($('.combinedopenended-editor'))
saveResult = @descriptor.save()
expect(saveResult.metadata.markdown).toEqual('markdown')
expect(saveResult.data).toEqual('<combinedopenended>\nmarkdown\n</combinedopenended>')
it 'clears markdown when xml editor is selected', ->
loadFixtures 'combinedopenended-with-markdown.html'
@descriptor = new OpenEndedMarkdownEditingDescriptor($('.combinedopenended-editor'))
@descriptor.createXMLEditor('replace with markdown')
saveResult = @descriptor.save()
expect(saveResult.metadata.markdown).toEqual(null)
expect(saveResult.data).toEqual('replace with markdown')
it 'saves xml from the xml editor', ->
loadFixtures 'combinedopenended-without-markdown.html'
@descriptor = new OpenEndedMarkdownEditingDescriptor($('.combinedopenended-editor'))
saveResult = @descriptor.save()
expect(saveResult.metadata.markdown).toEqual(null)
expect(saveResult.data).toEqual('xml only')
describe 'insertPrompt', ->
it 'inserts the template if selection is empty', ->
revisedSelection = OpenEndedMarkdownEditingDescriptor.insertPrompt('')
expect(revisedSelection).toEqual(OpenEndedMarkdownEditingDescriptor.promptTemplate)
it 'recognizes html in the prompt', ->
revisedSelection = OpenEndedMarkdownEditingDescriptor.insertPrompt('[prompt]<h1>Hello</h1>[prompt]')
expect(revisedSelection).toEqual('[prompt]<h1>Hello</h1>[prompt]')
describe 'insertRubric', ->
it 'inserts the template if selection is empty', ->
revisedSelection = OpenEndedMarkdownEditingDescriptor.insertRubric('')
expect(revisedSelection).toEqual(OpenEndedMarkdownEditingDescriptor.rubricTemplate)
it 'recognizes a proper rubric', ->
revisedSelection = OpenEndedMarkdownEditingDescriptor.insertRubric('[rubric]\n+1\n-1\n-2\n[rubric]')
expect(revisedSelection).toEqual('[rubric]\n+1\n-1\n-2\n[rubric]')
describe 'insertTasks', ->
it 'inserts the template if selection is empty', ->
revisedSelection = OpenEndedMarkdownEditingDescriptor.insertTasks('')
expect(revisedSelection).toEqual(OpenEndedMarkdownEditingDescriptor.tasksTemplate)
it 'recognizes a proper task string', ->
revisedSelection = OpenEndedMarkdownEditingDescriptor.insertTasks('[tasks](Self)[tasks]')
expect(revisedSelection).toEqual('[tasks](Self)[tasks]')
describe 'markdownToXml', ->
# test default templates
it 'converts prompt to xml', ->
data = OpenEndedMarkdownEditingDescriptor.markdownToXml("""[prompt]
<h1>Prompt!</h1>
This is my super awesome prompt.
[prompt]
""")
data = data.replace(/[\t\n\s]/gmi,'')
expect(data).toEqual("""
<combinedopenended>
<prompt>
<h1>Prompt!</h1>
This is my super awesome prompt.
</prompt>
</combinedopenended>
""".replace(/[\t\n\s]/gmi,''))
it 'converts rubric to xml', ->
data = OpenEndedMarkdownEditingDescriptor.markdownToXml("""[rubric]
+ 1
-1
-2
+ 2
-1
-2
+3
-1
-2
-3
[rubric]
""")
data = data.replace(/[\t\n\s]/gmi,'')
expect(data).toEqual("""
<combinedopenended>
<rubric>
<rubric>
<category>
<description>1</description>
<option>1</option>
<option>2</option>
</category>
<category>
<description>2</description>
<option>1</option>
<option>2</option>
</category>
<category>
<description>3</description>
<option>1</option>
<option>2</option>
<option>3</option>
</category>
</rubric>
</rubric>
</combinedopenended>
""".replace(/[\t\n\s]/gmi,''))
it 'converts tasks to xml', ->
data = OpenEndedMarkdownEditingDescriptor.markdownToXml("""[tasks]
(Self), ({1-2}AI), ({1-4}AI), ({1-2}Peer
[tasks]
""")
data = data.replace(/[\t\n\s]/gmi,'')
equality_list = """
<combinedopenended>
<task>
<selfassessment/>
</task>
<task>
<openended min_score_to_attempt="1" max_score_to_attempt="2">ml_grading.conf</openended>
</task>
<task>
<openended min_score_to_attempt="1" max_score_to_attempt="4">ml_grading.conf</openended>
</task>
<task>
<openended min_score_to_attempt="1" max_score_to_attempt="2">peer_grading.conf</openended>
</task>
</combinedopenended>
"""
expect(data).toEqual(equality_list.replace(/[\t\n\s]/gmi,''))
...@@ -847,8 +847,8 @@ class CombinedOpenEndedV1Descriptor(): ...@@ -847,8 +847,8 @@ class CombinedOpenEndedV1Descriptor():
if len(xml_object.xpath(child)) == 0: if len(xml_object.xpath(child)) == 0:
#This is a staff_facing_error #This is a staff_facing_error
raise ValueError( raise ValueError(
"Combined Open Ended definition must include at least one '{0}' tag. Contact the learning sciences group for assistance.".format( "Combined Open Ended definition must include at least one '{0}' tag. Contact the learning sciences group for assistance. {1}".format(
child)) child, xml_object))
def parse_task(k): def parse_task(k):
"""Assumes that xml_object has child k""" """Assumes that xml_object has child k"""
......
...@@ -53,8 +53,9 @@ class GradingService(object): ...@@ -53,8 +53,9 @@ class GradingService(object):
except (RequestException, ConnectionError, HTTPError) as err: except (RequestException, ConnectionError, HTTPError) as err:
# reraise as promised GradingServiceError, but preserve stacktrace. # reraise as promised GradingServiceError, but preserve stacktrace.
#This is a dev_facing_error #This is a dev_facing_error
log.error("Problem posting data to the grading controller. URL: {0}, data: {1}".format(url, data)) error_string = "Problem posting data to the grading controller. URL: {0}, data: {1}".format(url, data)
raise GradingServiceError, str(err), sys.exc_info()[2] log.error(error_string)
raise GradingServiceError(error_string)
return r.text return r.text
...@@ -71,8 +72,9 @@ class GradingService(object): ...@@ -71,8 +72,9 @@ class GradingService(object):
except (RequestException, ConnectionError, HTTPError) as err: except (RequestException, ConnectionError, HTTPError) as err:
# reraise as promised GradingServiceError, but preserve stacktrace. # reraise as promised GradingServiceError, but preserve stacktrace.
#This is a dev_facing_error #This is a dev_facing_error
log.error("Problem getting data from the grading controller. URL: {0}, params: {1}".format(url, params)) error_string = "Problem getting data from the grading controller. URL: {0}, params: {1}".format(url, params)
raise GradingServiceError, str(err), sys.exc_info()[2] log.error(error_string)
raise GradingServiceError(error_string)
return r.text return r.text
......
...@@ -168,7 +168,10 @@ class OpenEndedModule(openendedchild.OpenEndedChild): ...@@ -168,7 +168,10 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
#This is a student_facing_error #This is a student_facing_error
return {'success': False, 'msg': "There was an error saving your feedback. Please contact course staff."} return {'success': False, 'msg': "There was an error saving your feedback. Please contact course staff."}
qinterface = system.xqueue['interface'] xqueue = system.get('xqueue')
if xqueue is None:
return {'success': False, 'msg': "Couldn't submit feedback."}
qinterface = xqueue['interface']
qtime = datetime.strftime(datetime.now(), xqueue_interface.dateformat) qtime = datetime.strftime(datetime.now(), xqueue_interface.dateformat)
anonymous_student_id = system.anonymous_student_id anonymous_student_id = system.anonymous_student_id
queuekey = xqueue_interface.make_hashkey(str(system.seed) + qtime + queuekey = xqueue_interface.make_hashkey(str(system.seed) + qtime +
...@@ -176,7 +179,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild): ...@@ -176,7 +179,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
str(len(self.child_history))) str(len(self.child_history)))
xheader = xqueue_interface.make_xheader( xheader = xqueue_interface.make_xheader(
lms_callback_url=system.xqueue['construct_callback'](), lms_callback_url=xqueue['construct_callback'](),
lms_key=queuekey, lms_key=queuekey,
queue_name=self.message_queue_name queue_name=self.message_queue_name
) )
...@@ -219,7 +222,10 @@ class OpenEndedModule(openendedchild.OpenEndedChild): ...@@ -219,7 +222,10 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
# Prepare xqueue request # Prepare xqueue request
#------------------------------------------------------------ #------------------------------------------------------------
qinterface = system.xqueue['interface'] xqueue = system.get('xqueue')
if xqueue is None:
return False
qinterface = xqueue['interface']
qtime = datetime.strftime(datetime.now(), xqueue_interface.dateformat) qtime = datetime.strftime(datetime.now(), xqueue_interface.dateformat)
anonymous_student_id = system.anonymous_student_id anonymous_student_id = system.anonymous_student_id
...@@ -230,7 +236,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild): ...@@ -230,7 +236,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
str(len(self.child_history))) str(len(self.child_history)))
xheader = xqueue_interface.make_xheader( xheader = xqueue_interface.make_xheader(
lms_callback_url=system.xqueue['construct_callback'](), lms_callback_url=xqueue['construct_callback'](),
lms_key=queuekey, lms_key=queuekey,
queue_name=self.queue_name queue_name=self.queue_name
) )
......
...@@ -10,7 +10,7 @@ from .x_module import XModule ...@@ -10,7 +10,7 @@ from .x_module import XModule
from xmodule.raw_module import RawDescriptor from xmodule.raw_module import RawDescriptor
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from .timeinfo import TimeInfo from .timeinfo import TimeInfo
from xblock.core import Object, String, Scope from xblock.core import Object, Integer, Boolean, String, Scope
from xmodule.fields import Date, StringyFloat, StringyInteger, StringyBoolean from xmodule.fields import Date, StringyFloat, StringyInteger, StringyBoolean
from xmodule.open_ended_grading_classes.peer_grading_service import PeerGradingService, GradingServiceError, MockPeerGradingService from xmodule.open_ended_grading_classes.peer_grading_service import PeerGradingService, GradingServiceError, MockPeerGradingService
...@@ -32,13 +32,20 @@ class PeerGradingFields(object): ...@@ -32,13 +32,20 @@ class PeerGradingFields(object):
help='When True, only the single problem specified by "Link to Problem Location" is shown. ' help='When True, only the single problem specified by "Link to Problem Location" is shown. '
'When False, a panel is displayed with all problems available for peer grading.', 'When False, a panel is displayed with all problems available for peer grading.',
default=USE_FOR_SINGLE_LOCATION, scope=Scope.settings) default=USE_FOR_SINGLE_LOCATION, scope=Scope.settings)
link_to_location = String(display_name="Link to Problem Location", link_to_location = String(display_name="Link to Problem Location",
help='The location of the problem being graded. Only used when "Show Single Problem" is True.', help='The location of the problem being graded. Only used when "Show Single Problem" is True.',
default=LINK_TO_LOCATION, scope=Scope.settings) default=LINK_TO_LOCATION, scope=Scope.settings)
# TODO: move boolean default into xfields # TODO: move boolean default into xfields
is_graded = StringyBoolean(display_name="Graded", is_graded = StringyBoolean(display_name="Graded",
help='Whether the student gets credit for grading this problem. Only used when "Show Single Problem" is True.', help='Whether the student gets credit for grading this problem. Only used when "Show Single Problem" is True.',
default=IS_GRADED, scope=Scope.settings) default=IS_GRADED, scope=Scope.settings)
use_for_single_location = StringyBoolean(help="Whether to use this for a single location or as a panel.",
default=USE_FOR_SINGLE_LOCATION, scope=Scope.settings)
link_to_location = String(help="The location this problem is linked to.", default=LINK_TO_LOCATION,
scope=Scope.settings)
is_graded = StringyBoolean(help="Whether or not this module is scored.", default=IS_GRADED, scope=Scope.settings)
due_date = Date(help="Due date that should be displayed.", default=None, scope=Scope.settings) due_date = Date(help="Due date that should be displayed.", default=None, scope=Scope.settings)
grace_period_string = String(help="Amount of grace to give on the due date.", default=None, scope=Scope.settings) grace_period_string = String(help="Amount of grace to give on the due date.", default=None, scope=Scope.settings)
max_grade = StringyInteger(help="The maximum grade that a student can receive for this problem.", default=MAX_SCORE, max_grade = StringyInteger(help="The maximum grade that a student can receive for this problem.", default=MAX_SCORE,
...@@ -596,6 +603,9 @@ class PeerGradingDescriptor(PeerGradingFields, RawDescriptor): ...@@ -596,6 +603,9 @@ class PeerGradingDescriptor(PeerGradingFields, RawDescriptor):
always_recalculate_grades = True always_recalculate_grades = True
template_dir_name = "peer_grading" template_dir_name = "peer_grading"
#Specify whether or not to pass in open ended interface
needs_open_ended_interface = True
@property @property
def non_editable_metadata_fields(self): def non_editable_metadata_fields(self):
non_editable_fields = super(PeerGradingDescriptor, self).non_editable_metadata_fields non_editable_fields = super(PeerGradingDescriptor, self).non_editable_metadata_fields
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
metadata: metadata:
display_name: Open Ended Response display_name: Open Ended Response
version: 1 version: 1
markdown: ""
data: | data: |
<combinedopenended> <combinedopenended>
<rubric> <rubric>
......
...@@ -407,7 +407,7 @@ class CombinedOpenEndedModuleTest(unittest.TestCase): ...@@ -407,7 +407,7 @@ class CombinedOpenEndedModuleTest(unittest.TestCase):
self.assertTrue(changed) self.assertTrue(changed)
def test_get_max_score(self): def test_get_max_score(self):
changed = self.combinedoe.update_task_states() self.combinedoe.update_task_states()
self.combinedoe.state = "done" self.combinedoe.state = "done"
self.combinedoe.is_scored = True self.combinedoe.is_scored = True
max_score = self.combinedoe.max_score() max_score = self.combinedoe.max_score()
...@@ -611,11 +611,11 @@ class OpenEndedModuleXmlTest(unittest.TestCase, DummyModulestore): ...@@ -611,11 +611,11 @@ class OpenEndedModuleXmlTest(unittest.TestCase, DummyModulestore):
self.assertEqual(module.current_task_number, 1) self.assertEqual(module.current_task_number, 1)
#Get html and other data client will request #Get html and other data client will request
html = module.get_html() module.get_html()
legend = module.handle_ajax("get_legend", {}) legend = module.handle_ajax("get_legend", {})
self.assertTrue(isinstance(legend, basestring)) self.assertTrue(isinstance(legend, basestring))
status = module.handle_ajax("get_status", {}) module.handle_ajax("get_status", {})
module.handle_ajax("skip_post_assessment", {}) module.handle_ajax("skip_post_assessment", {})
self.assertTrue(isinstance(legend, basestring)) self.assertTrue(isinstance(legend, basestring))
......
...@@ -214,21 +214,26 @@ def get_module_for_descriptor(user, request, descriptor, model_data_cache, cours ...@@ -214,21 +214,26 @@ def get_module_for_descriptor(user, request, descriptor, model_data_cache, cours
#This is a hacky way to pass settings to the combined open ended xmodule #This is a hacky way to pass settings to the combined open ended xmodule
#It needs an S3 interface to upload images to S3 #It needs an S3 interface to upload images to S3
#It needs the open ended grading interface in order to get peer grading to be done #It needs the open ended grading interface in order to get peer grading to be done
#TODO: refactor these settings into module-specific settings when possible.
#this first checks to see if the descriptor is the correct one, and only sends settings if it is #this first checks to see if the descriptor is the correct one, and only sends settings if it is
is_descriptor_combined_open_ended = (descriptor.__class__.__name__ == 'CombinedOpenEndedDescriptor')
is_descriptor_peer_grading = (descriptor.__class__.__name__ == 'PeerGradingDescriptor') #Get descriptor metadata fields indicating needs for various settings
needs_open_ended_interface = getattr(descriptor, "needs_open_ended_interface", False)
needs_s3_interface = getattr(descriptor, "needs_s3_interface", False)
#Initialize interfaces to None
open_ended_grading_interface = None open_ended_grading_interface = None
s3_interface = None s3_interface = None
if is_descriptor_combined_open_ended or is_descriptor_peer_grading:
#Create interfaces if needed
if needs_open_ended_interface:
open_ended_grading_interface = settings.OPEN_ENDED_GRADING_INTERFACE open_ended_grading_interface = settings.OPEN_ENDED_GRADING_INTERFACE
open_ended_grading_interface['mock_peer_grading'] = settings.MOCK_PEER_GRADING open_ended_grading_interface['mock_peer_grading'] = settings.MOCK_PEER_GRADING
open_ended_grading_interface['mock_staff_grading'] = settings.MOCK_STAFF_GRADING open_ended_grading_interface['mock_staff_grading'] = settings.MOCK_STAFF_GRADING
if is_descriptor_combined_open_ended: if needs_s3_interface:
s3_interface = { s3_interface = {
'access_key' : getattr(settings,'AWS_ACCESS_KEY_ID',''), 'access_key': getattr(settings, 'AWS_ACCESS_KEY_ID', ''),
'secret_access_key' : getattr(settings,'AWS_SECRET_ACCESS_KEY',''), 'secret_access_key': getattr(settings, 'AWS_SECRET_ACCESS_KEY', ''),
'storage_bucket_name' : getattr(settings,'AWS_STORAGE_BUCKET_NAME','openended') 'storage_bucket_name': getattr(settings, 'AWS_STORAGE_BUCKET_NAME', 'openended')
} }
def inner_get_module(descriptor): def inner_get_module(descriptor):
......
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