Commit a2522c49 by Vik Paruchuri

Merge pull request #160 from edx/fix/vik/studio-oe

Fix/vik/studio oe
parents 66ca7d75 a478fa9f
...@@ -140,7 +140,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): ...@@ -140,7 +140,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
self.check_components_on_page(ADVANCED_COMPONENT_TYPES, ['Video Alpha', self.check_components_on_page(ADVANCED_COMPONENT_TYPES, ['Video Alpha',
'Word cloud', 'Word cloud',
'Annotation', 'Annotation',
'Open Ended Grading', 'Open Response Assessment',
'Peer Grading Interface']) 'Peer Grading Interface'])
def test_advanced_components_require_two_clicks(self): def test_advanced_components_require_two_clicks(self):
......
...@@ -13,7 +13,7 @@ import textwrap ...@@ -13,7 +13,7 @@ import textwrap
log = logging.getLogger("mitx.courseware") log = logging.getLogger("mitx.courseware")
V1_SETTINGS_ATTRIBUTES = ["display_name", "attempts", "is_graded", "accept_file_upload", V1_SETTINGS_ATTRIBUTES = ["display_name", "max_attempts", "graded", "accept_file_upload",
"skip_spelling_checks", "due", "graceperiod", "weight"] "skip_spelling_checks", "due", "graceperiod", "weight"]
V1_STUDENT_ATTRIBUTES = ["current_task_number", "task_states", "state", V1_STUDENT_ATTRIBUTES = ["current_task_number", "task_states", "state",
...@@ -29,36 +29,124 @@ VERSION_TUPLES = { ...@@ -29,36 +29,124 @@ VERSION_TUPLES = {
DEFAULT_VERSION = 1 DEFAULT_VERSION = 1
DEFAULT_DATA = textwrap.dedent("""\ DEFAULT_DATA = textwrap.dedent("""\
<combinedopenended> <combinedopenended>
<prompt>
<h3>Censorship in the Libraries</h3>
<p>'All of us can think of a book that we hope none of our children or any other children have taken off the shelf. But if I have the right to remove that book from the shelf -- that work I abhor -- then you also have exactly the same right and so does everyone else. And then we have no books left on the shelf for any of us.' --Katherine Paterson, Author
</p>
<p>
Write a persuasive essay to a newspaper reflecting your vies on censorship in libraries. Do you believe that certain materials, such as books, music, movies, magazines, etc., should be removed from the shelves if they are found offensive? Support your position with convincing arguments from your own experience, observations, and/or reading.
</p>
</prompt>
<rubric> <rubric>
<rubric> <rubric>
<category> <category>
<description>Category 1</description> <description>
Ideas
</description>
<option>
Difficult for the reader to discern the main idea. Too brief or too repetitive to establish or maintain a focus.
</option>
<option>
Attempts a main idea. Sometimes loses focus or ineffectively displays focus.
</option>
<option>
Presents a unifying theme or main idea, but may include minor tangents. Stays somewhat focused on topic and task.
</option>
<option>
Presents a unifying theme or main idea without going off on tangents. Stays completely focused on topic and task.
</option>
</category>
<category>
<description>
Content
</description>
<option>
Includes little information with few or no details or unrelated details. Unsuccessful in attempts to explore any facets of the topic.
</option>
<option>
Includes little information and few or no details. Explores only one or two facets of the topic.
</option>
<option> <option>
The response does not incorporate what is needed for a one response. Includes sufficient information and supporting details. (Details may not be fully developed; ideas may be listed.) Explores some facets of the topic.
</option> </option>
<option> <option>
The response is correct for category 1. Includes in-depth information and exceptional supporting details that are fully developed. Explores all facets of the topic.
</option> </option>
</category> </category>
<category>
<description>
Organization
</description>
<option>
Ideas organized illogically, transitions weak, and response difficult to follow.
</option>
<option>
Attempts to logically organize ideas. Attempts to progress in an order that enhances meaning, and demonstrates use of transitions.
</option>
<option>
Ideas organized logically. Progresses in an order that enhances meaning. Includes smooth transitions.
</option>
</category>
<category>
<description>
Style
</description>
<option>
Contains limited vocabulary, with many words used incorrectly. Demonstrates problems with sentence patterns.
</option>
<option>
Contains basic vocabulary, with words that are predictable and common. Contains mostly simple sentences (although there may be an attempt at more varied sentence patterns).
</option>
<option>
Includes vocabulary to make explanations detailed and precise. Includes varied sentence patterns, including complex sentences.
</option>
</category>
<category>
<description>
Voice
</description>
<option>
Demonstrates language and tone that may be inappropriate to task and reader.
</option>
<option>
Demonstrates an attempt to adjust language and tone to task and reader.
</option>
<option>
Demonstrates effective adjustment of language and tone to task and reader.
</option>
</category>
</rubric> </rubric>
</rubric> </rubric>
<prompt>
<p>Why is the sky blue?</p>
</prompt>
<task> <task>
<selfassessment/> <selfassessment/></task>
<task>
<openended min_score_to_attempt="4" max_score_to_attempt="12" >
<openendedparam>
<initial_display>Enter essay here.</initial_display>
<answer_display>This is the answer.</answer_display>
<grader_payload>{"grader_settings" : "ml_grading.conf", "problem_id" : "6.002x/Welcome/OETest"}</grader_payload>
</openendedparam>
</openended>
</task> </task>
<task> <task>
<openended min_score_to_attempt="1" max_score_to_attempt="2">
<openended min_score_to_attempt="9" max_score_to_attempt="12" >
<openendedparam> <openendedparam>
<initial_display>Enter essay here.</initial_display> <initial_display>Enter essay here.</initial_display>
<answer_display>This is the answer.</answer_display> <answer_display>This is the answer.</answer_display>
<grader_payload>{"grader_settings" : "peer_grading.conf", "problem_id" : "700x/Demo"}</grader_payload> <grader_payload>{"grader_settings" : "peer_grading.conf", "problem_id" : "6.002x/Welcome/OETest"}</grader_payload>
</openendedparam> </openendedparam>
</openended> </openended>
</task> </task>
</combinedopenended>
</combinedopenended>
""") """)
...@@ -84,35 +172,63 @@ class CombinedOpenEndedFields(object): ...@@ -84,35 +172,63 @@ class CombinedOpenEndedFields(object):
display_name = String( display_name = String(
display_name="Display Name", display_name="Display Name",
help="This name appears in the horizontal navigation at the top of the page.", help="This name appears in the horizontal navigation at the top of the page.",
default="Open Ended Grading", default="Open Response Assessment",
scope=Scope.settings scope=Scope.settings
) )
current_task_number = Integer(help="Current task that the student is on.", default=0, scope=Scope.user_state) current_task_number = Integer(
task_states = List(help="List of state dictionaries of each task within this module.", scope=Scope.user_state) help="Current task that the student is on.",
state = String(help="Which step within the current task that the student is on.", default="initial", default=0,
scope=Scope.user_state) scope=Scope.user_state
student_attempts = Integer(help="Number of attempts taken by the student on this problem", default=0, )
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",
scope=Scope.user_state
)
graded = Boolean(
display_name="Graded",
help='Defines whether the student gets credit for grading this problem.',
default=False,
scope=Scope.settings
)
student_attempts = Integer(
help="Number of attempts taken by the student on this problem",
default=0,
scope=Scope.user_state
)
ready_to_reset = Boolean( ready_to_reset = Boolean(
help="If the problem is ready to be reset or not.", default=False, help="If the problem is ready to be reset or not.",
default=False,
scope=Scope.user_state scope=Scope.user_state
) )
attempts = Integer( max_attempts = Integer(
display_name="Maximum Attempts", 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.",
scope=Scope.settings, values={"min" : 1 } default=1,
scope=Scope.settings,
values={"min" : 1 }
) )
is_graded = Boolean(display_name="Graded", help="Whether or not the problem is graded.", default=False, scope=Scope.settings)
accept_file_upload = Boolean( accept_file_upload = Boolean(
display_name="Allow File Uploads", display_name="Allow File Uploads",
help="Whether or not the student can submit files as a response.", default=False, scope=Scope.settings help="Whether or not the student can submit files as a response.",
default=False,
scope=Scope.settings
) )
skip_spelling_checks = Boolean( skip_spelling_checks = Boolean(
display_name="Disable Quality Filter", display_name="Disable Quality Filter",
help="If False, the Quality Filter is enabled and submissions with poor spelling, short length, or poor grammar will not be peer reviewed.", help="If False, the Quality Filter is enabled and submissions with poor spelling, short length, or poor grammar will not be peer reviewed.",
default=False, scope=Scope.settings default=False,
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( graceperiod = String(
help="Amount of time after the due date that submissions will be accepted", help="Amount of time after the due date that submissions will be accepted",
default=None, default=None,
...@@ -124,22 +240,51 @@ class CombinedOpenEndedFields(object): ...@@ -124,22 +240,51 @@ class CombinedOpenEndedFields(object):
weight = Float( weight = Float(
display_name="Problem Weight", display_name="Problem Weight",
help="Defines the number of points each problem is worth. If the value is not set, each problem is worth one point.", help="Defines the number of points each problem is worth. If the value is not set, each problem is worth one point.",
scope=Scope.settings, values={"min" : 0 , "step": ".1"} scope=Scope.settings,
values={"min" : 0 , "step": ".1"},
default=1
) )
markdown = String( markdown = String(
help="Markdown source of this module", help="Markdown source of this module",
default=textwrap.dedent("""\ default=textwrap.dedent("""\
[rubric]
+ Category 1
- The response does not incorporate what is needed for a one response.
- The response is correct for category 1.
[rubric]
[prompt] [prompt]
<p>Why is the sky blue?</p> <h3>Censorship in the Libraries</h3>
<p>'All of us can think of a book that we hope none of our children or any other children have taken off the shelf. But if I have the right to remove that book from the shelf -- that work I abhor -- then you also have exactly the same right and so does everyone else. And then we have no books left on the shelf for any of us.' --Katherine Paterson, Author
</p>
<p>
Write a persuasive essay to a newspaper reflecting your vies on censorship in libraries. Do you believe that certain materials, such as books, music, movies, magazines, etc., should be removed from the shelves if they are found offensive? Support your position with convincing arguments from your own experience, observations, and/or reading.
</p>
[prompt] [prompt]
[rubric]
+ Ideas
- Difficult for the reader to discern the main idea. Too brief or too repetitive to establish or maintain a focus.
- Attempts a main idea. Sometimes loses focus or ineffectively displays focus.
- Presents a unifying theme or main idea, but may include minor tangents. Stays somewhat focused on topic and task.
- Presents a unifying theme or main idea without going off on tangents. Stays completely focused on topic and task.
+ Content
- Includes little information with few or no details or unrelated details. Unsuccessful in attempts to explore any facets of the topic.
- Includes little information and few or no details. Explores only one or two facets of the topic.
- Includes sufficient information and supporting details. (Details may not be fully developed; ideas may be listed.) Explores some facets of the topic.
- Includes in-depth information and exceptional supporting details that are fully developed. Explores all facets of the topic.
+ Organization
- Ideas organized illogically, transitions weak, and response difficult to follow.
- Attempts to logically organize ideas. Attempts to progress in an order that enhances meaning, and demonstrates use of transitions.
- Ideas organized logically. Progresses in an order that enhances meaning. Includes smooth transitions.
+ Style
- Contains limited vocabulary, with many words used incorrectly. Demonstrates problems with sentence patterns.
- Contains basic vocabulary, with words that are predictable and common. Contains mostly simple sentences (although there may be an attempt at more varied sentence patterns).
- Includes vocabulary to make explanations detailed and precise. Includes varied sentence patterns, including complex sentences.
+ Voice
- Demonstrates language and tone that may be inappropriate to task and reader.
- Demonstrates an attempt to adjust language and tone to task and reader.
- Demonstrates effective adjustment of language and tone to task and reader.
[rubric]
[tasks] [tasks]
(Self), ({1-2}AI) (Self), ({4-12}AI), ({9-12}Peer)
[tasks] [tasks]
"""), """),
scope=Scope.settings scope=Scope.settings
) )
...@@ -194,37 +339,9 @@ class CombinedOpenEndedModule(CombinedOpenEndedFields, XModule): ...@@ -194,37 +339,9 @@ class CombinedOpenEndedModule(CombinedOpenEndedFields, XModule):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
""" """
Definition file should have one or many task blocks, a rubric block, and a prompt block: Definition file should have one or many task blocks, a rubric block, and a prompt block.
Sample file: See DEFAULT_DATA for a sample.
<combinedopenended attempts="10000">
<rubric>
Blah blah rubric.
</rubric>
<prompt>
Some prompt.
</prompt>
<task>
<selfassessment>
<hintprompt>
What hint about this problem would you give to someone?
</hintprompt>
<submitmessage>
Save Succcesful. Thanks for participating!
</submitmessage>
</selfassessment>
</task>
<task>
<openended min_score_to_attempt="1" max_score_to_attempt="1">
<openendedparam>
<initial_display>Enter essay here.</initial_display>
<answer_display>This is the answer.</answer_display>
<grader_payload>{"grader_settings" : "ml_grading.conf",
"problem_id" : "6.002x/Welcome/OETest"}</grader_payload>
</openendedparam>
</openended>
</task>
</combinedopenended>
""" """
XModule.__init__(self, *args, **kwargs) XModule.__init__(self, *args, **kwargs)
...@@ -291,6 +408,7 @@ class CombinedOpenEndedDescriptor(CombinedOpenEndedFields, RawDescriptor): ...@@ -291,6 +408,7 @@ class CombinedOpenEndedDescriptor(CombinedOpenEndedFields, RawDescriptor):
has_score = True has_score = True
always_recalculate_grades = True always_recalculate_grades = True
template_dir_name = "combinedopenended"
#Specify whether or not to pass in S3 interface #Specify whether or not to pass in S3 interface
needs_s3_interface = True needs_s3_interface = True
...@@ -304,6 +422,11 @@ class CombinedOpenEndedDescriptor(CombinedOpenEndedFields, RawDescriptor): ...@@ -304,6 +422,11 @@ class CombinedOpenEndedDescriptor(CombinedOpenEndedFields, RawDescriptor):
js_module_name = "OpenEndedMarkdownEditingDescriptor" js_module_name = "OpenEndedMarkdownEditingDescriptor"
css = {'scss': [resource_string(__name__, 'css/editor/edit.scss'), resource_string(__name__, 'css/combinedopenended/edit.scss')]} css = {'scss': [resource_string(__name__, 'css/editor/edit.scss'), resource_string(__name__, 'css/combinedopenended/edit.scss')]}
metadata_translations = {
'is_graded': 'graded',
'attempts': 'max_attempts',
}
def get_context(self): def get_context(self):
_context = RawDescriptor.get_context(self) _context = RawDescriptor.get_context(self)
_context.update({'markdown': self.markdown, _context.update({'markdown': self.markdown,
......
...@@ -50,6 +50,10 @@ Write a persuasive essay to a newspaper reflecting your vies on censorship in li ...@@ -50,6 +50,10 @@ Write a persuasive essay to a newspaper reflecting your vies on censorship in li
mode: null mode: null
}) })
@setCurrentEditor(@markdown_editor) @setCurrentEditor(@markdown_editor)
selection = @markdown_editor.getSelection()
#Auto-add in the needed template if it isn't already in there.
if(@markdown_editor.getValue() == "")
@markdown_editor.setValue(OpenEndedMarkdownEditingDescriptor.promptTemplate + "\n" + OpenEndedMarkdownEditingDescriptor.rubricTemplate + "\n" + OpenEndedMarkdownEditingDescriptor.tasksTemplate)
# Add listeners for toolbar buttons (only present for markdown editor) # Add listeners for toolbar buttons (only present for markdown editor)
@element.on('click', '.xml-tab', @onShowXMLButton) @element.on('click', '.xml-tab', @onShowXMLButton)
@element.on('click', '.format-buttons a', @onToolbarButton) @element.on('click', '.format-buttons a', @onToolbarButton)
......
...@@ -78,37 +78,7 @@ class CombinedOpenEndedV1Module(): ...@@ -78,37 +78,7 @@ class CombinedOpenEndedV1Module():
instance_state=None, shared_state=None, metadata=None, static_data=None, **kwargs): instance_state=None, shared_state=None, metadata=None, static_data=None, **kwargs):
""" """
Definition file should have one or many task blocks, a rubric block, and a prompt block: Definition file should have one or many task blocks, a rubric block, and a prompt block. See DEFAULT_DATA in combined_open_ended_module for a sample.
Sample file:
<combinedopenended attempts="10000">
<rubric>
Blah blah rubric.
</rubric>
<prompt>
Some prompt.
</prompt>
<task>
<selfassessment>
<hintprompt>
What hint about this problem would you give to someone?
</hintprompt>
<submitmessage>
Save Succcesful. Thanks for participating!
</submitmessage>
</selfassessment>
</task>
<task>
<openended min_score_to_attempt="1" max_score_to_attempt="1">
<openendedparam>
<initial_display>Enter essay here.</initial_display>
<answer_display>This is the answer.</answer_display>
<grader_payload>{"grader_settings" : "ml_grading.conf",
"problem_id" : "6.002x/Welcome/OETest"}</grader_payload>
</openendedparam>
</openended>
</task>
</combinedopenended>
""" """
...@@ -131,14 +101,14 @@ class CombinedOpenEndedV1Module(): ...@@ -131,14 +101,14 @@ class CombinedOpenEndedV1Module():
# Allow reset is true if student has failed the criteria to move to the next child task # Allow reset is true if student has failed the criteria to move to the next child task
self.ready_to_reset = instance_state.get('ready_to_reset', False) self.ready_to_reset = instance_state.get('ready_to_reset', False)
self.attempts = self.instance_state.get('attempts', MAX_ATTEMPTS) self.max_attempts = instance_state.get('max_attempts', MAX_ATTEMPTS)
self.is_scored = self.instance_state.get('is_graded', IS_SCORED) in TRUE_DICT self.is_scored = instance_state.get('graded', IS_SCORED) in TRUE_DICT
self.accept_file_upload = self.instance_state.get('accept_file_upload', ACCEPT_FILE_UPLOAD) in TRUE_DICT self.accept_file_upload = instance_state.get('accept_file_upload', ACCEPT_FILE_UPLOAD) in TRUE_DICT
self.skip_basic_checks = self.instance_state.get('skip_spelling_checks', SKIP_BASIC_CHECKS) in TRUE_DICT self.skip_basic_checks = instance_state.get('skip_spelling_checks', SKIP_BASIC_CHECKS) in TRUE_DICT
due_date = self.instance_state.get('due', None) due_date = instance_state.get('due', None)
grace_period_string = self.instance_state.get('graceperiod', None) grace_period_string = instance_state.get('graceperiod', None)
try: try:
self.timeinfo = TimeInfo(due_date, grace_period_string) self.timeinfo = TimeInfo(due_date, grace_period_string)
except Exception: except Exception:
...@@ -153,7 +123,7 @@ class CombinedOpenEndedV1Module(): ...@@ -153,7 +123,7 @@ class CombinedOpenEndedV1Module():
# Static data is passed to the child modules to render # Static data is passed to the child modules to render
self.static_data = { self.static_data = {
'max_score': self._max_score, 'max_score': self._max_score,
'max_attempts': self.attempts, 'max_attempts': self.max_attempts,
'prompt': definition['prompt'], 'prompt': definition['prompt'],
'rubric': definition['rubric'], 'rubric': definition['rubric'],
'display_name': self.display_name, 'display_name': self.display_name,
...@@ -643,15 +613,18 @@ class CombinedOpenEndedV1Module(): ...@@ -643,15 +613,18 @@ class CombinedOpenEndedV1Module():
if not self.ready_to_reset: if not self.ready_to_reset:
return self.out_of_sync_error(data) return self.out_of_sync_error(data)
if self.student_attempts > self.attempts: if self.student_attempts >= self.max_attempts-1:
if self.student_attempts==self.max_attempts-1:
self.student_attempts +=1
return { return {
'success': False, 'success': False,
# This is a student_facing_error # This is a student_facing_error
'error': ( 'error': (
'You have attempted this question {0} times. ' 'You have attempted this question {0} times. '
'You are only allowed to attempt it {1} times.' 'You are only allowed to attempt it {1} times.'
).format(self.student_attempts, self.attempts) ).format(self.student_attempts, self.max_attempts)
} }
self.student_attempts +=1
self.state = self.INITIAL self.state = self.INITIAL
self.ready_to_reset = False self.ready_to_reset = False
for i in xrange(0, len(self.task_xml)): for i in xrange(0, len(self.task_xml)):
...@@ -726,7 +699,12 @@ class CombinedOpenEndedV1Module(): ...@@ -726,7 +699,12 @@ class CombinedOpenEndedV1Module():
""" """
max_score = None max_score = None
score = None score = None
if self.is_scored and self.weight is not None:
#The old default was None, so set to 1 if it is the old default weight
weight = self.weight
if weight is None:
weight = 1
if self.is_scored:
# Finds the maximum score of all student attempts and keeps it. # Finds the maximum score of all student attempts and keeps it.
score_mat = [] score_mat = []
for i in xrange(0, len(self.task_states)): for i in xrange(0, len(self.task_states)):
...@@ -739,7 +717,7 @@ class CombinedOpenEndedV1Module(): ...@@ -739,7 +717,7 @@ class CombinedOpenEndedV1Module():
for z in xrange(0, len(score)): for z in xrange(0, len(score)):
if score[z] is None: if score[z] is None:
score[z] = 0 score[z] = 0
score[z] *= float(self.weight) score[z] *= float(weight)
score_mat.append(score) score_mat.append(score)
if len(score_mat) > 0: if len(score_mat) > 0:
...@@ -753,7 +731,7 @@ class CombinedOpenEndedV1Module(): ...@@ -753,7 +731,7 @@ class CombinedOpenEndedV1Module():
if max_score is not None: if max_score is not None:
# Weight the max score if it is not None # Weight the max score if it is not None
max_score *= float(self.weight) max_score *= float(weight)
else: else:
# Without a max_score, we cannot have a score! # Without a max_score, we cannot have a score!
score = None score = None
......
...@@ -9,6 +9,7 @@ from .capa_module import ComplexEncoder ...@@ -9,6 +9,7 @@ from .capa_module import ComplexEncoder
from .x_module import XModule 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 xmodule.modulestore.exceptions import ItemNotFoundError
from .timeinfo import TimeInfo from .timeinfo import TimeInfo
from xblock.core import Dict, String, Scope, Boolean, Integer, Float from xblock.core import Dict, String, Scope, Boolean, Integer, Float
from xmodule.fields import Date from xmodule.fields import Date
...@@ -19,36 +20,37 @@ from django.utils.timezone import UTC ...@@ -19,36 +20,37 @@ from django.utils.timezone import UTC
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
USE_FOR_SINGLE_LOCATION = False
LINK_TO_LOCATION = ""
MAX_SCORE = 1
IS_GRADED = False
EXTERNAL_GRADER_NO_CONTACT_ERROR = "Failed to contact external graders. Please notify course staff." EXTERNAL_GRADER_NO_CONTACT_ERROR = "Failed to contact external graders. Please notify course staff."
class PeerGradingFields(object): class PeerGradingFields(object):
use_for_single_location = Boolean( use_for_single_location = Boolean(
display_name="Show Single Problem", display_name="Show Single Problem",
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=False,
scope=Scope.settings
) )
link_to_location = String( link_to_location = String(
display_name="Link to Problem Location", 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="",
scope=Scope.settings
) )
is_graded = Boolean( graded = Boolean(
display_name="Graded", display_name="Graded",
help='Defines whether the student gets credit for grading this problem. Only used when "Show Single Problem" is True.', help='Defines whether the student gets credit for grading this problem. Only used when "Show Single Problem" is True.',
default=IS_GRADED, scope=Scope.settings default=False,
scope=Scope.settings
) )
due_date = Date(help="Due date that should be displayed.", default=None, scope=Scope.settings) due = Date(
grace_period_string = String(help="Amount of grace to give on the due date.", default=None, scope=Scope.settings) help="Due date that should be displayed.",
max_grade = Integer( default=None,
help="The maximum grade that a student can receive for this problem.", default=MAX_SCORE, scope=Scope.settings)
scope=Scope.settings, values={"min": 0} grace_period_string = String(
help="Amount of grace to give on the due date.",
default=None,
scope=Scope.settings
) )
student_data_for_location = Dict( student_data_for_location = Dict(
help="Student data for a given peer grading problem.", help="Student data for a given peer grading problem.",
...@@ -57,7 +59,8 @@ class PeerGradingFields(object): ...@@ -57,7 +59,8 @@ class PeerGradingFields(object):
weight = Float( weight = Float(
display_name="Problem Weight", display_name="Problem Weight",
help="Defines the number of points each problem is worth. If the value is not set, each problem is worth one point.", help="Defines the number of points each problem is worth. If the value is not set, each problem is worth one point.",
scope=Scope.settings, values={"min": 0, "step": ".1"} scope=Scope.settings, values={"min": 0, "step": ".1"},
default=1
) )
display_name = String( display_name = String(
display_name="Display Name", display_name="Display Name",
...@@ -98,35 +101,31 @@ class PeerGradingModule(PeerGradingFields, XModule): ...@@ -98,35 +101,31 @@ class PeerGradingModule(PeerGradingFields, XModule):
if self.use_for_single_location: if self.use_for_single_location:
try: try:
self.linked_problem = modulestore().get_instance(self.system.course_id, self.link_to_location) self.linked_problem = modulestore().get_instance(self.system.course_id, self.link_to_location)
except: except ItemNotFoundError:
log.error("Linked location {0} for peer grading module {1} does not exist".format( log.error("Linked location {0} for peer grading module {1} does not exist".format(
self.link_to_location, self.location)) self.link_to_location, self.location))
raise raise
due_date = self.linked_problem._model_data.get('peer_grading_due', None) due_date = self.linked_problem._model_data.get('due', None)
if due_date: if due_date:
self._model_data['due'] = due_date self._model_data['due'] = due_date
try: try:
self.timeinfo = TimeInfo(self.due_date, self.grace_period_string) self.timeinfo = TimeInfo(self.due, self.grace_period_string)
except: except Exception:
log.error("Error parsing due date information in location {0}".format(location)) log.error("Error parsing due date information in location {0}".format(self.location))
raise raise
self.display_due_date = self.timeinfo.display_due_date self.display_due_date = self.timeinfo.display_due_date
try: try:
self.student_data_for_location = json.loads(self.student_data_for_location) self.student_data_for_location = json.loads(self.student_data_for_location)
except: except Exception:
pass pass
self.ajax_url = self.system.ajax_url self.ajax_url = self.system.ajax_url
if not self.ajax_url.endswith("/"): if not self.ajax_url.endswith("/"):
self.ajax_url = self.ajax_url + "/" self.ajax_url = self.ajax_url + "/"
# Integer could return None, so keep this check.
if not isinstance(self.max_grade, int):
raise TypeError("max_grade needs to be an integer.")
def closed(self): def closed(self):
return self._closed(self.timeinfo) return self._closed(self.timeinfo)
...@@ -210,11 +209,16 @@ class PeerGradingModule(PeerGradingFields, XModule): ...@@ -210,11 +209,16 @@ class PeerGradingModule(PeerGradingFields, XModule):
def get_score(self): def get_score(self):
max_score = None max_score = None
score = None score = None
weight = self.weight
#The old default was None, so set to 1 if it is the old default weight
if weight is None:
weight = 1
score_dict = { score_dict = {
'score': score, 'score': score,
'total': max_score, 'total': max_score,
} }
if not self.use_for_single_location or not self.is_graded: if not self.use_for_single_location or not self.graded:
return score_dict return score_dict
try: try:
...@@ -234,9 +238,8 @@ class PeerGradingModule(PeerGradingFields, XModule): ...@@ -234,9 +238,8 @@ class PeerGradingModule(PeerGradingFields, XModule):
# Ensures that once a student receives a final score for peer grading, that it does not change. # Ensures that once a student receives a final score for peer grading, that it does not change.
self.student_data_for_location = response self.student_data_for_location = response
if self.weight is not None: score = int(count_graded >= count_required and count_graded > 0) * float(weight)
score = int(count_graded >= count_required and count_graded > 0) * float(self.weight) total = float(weight)
total = self.max_grade * float(self.weight)
score_dict['score'] = score score_dict['score'] = score
score_dict['total'] = total score_dict['total'] = total
...@@ -249,8 +252,8 @@ class PeerGradingModule(PeerGradingFields, XModule): ...@@ -249,8 +252,8 @@ class PeerGradingModule(PeerGradingFields, XModule):
randomization, and 5/7 on another randomization, and 5/7 on another
''' '''
max_grade = None max_grade = None
if self.use_for_single_location and self.is_graded: if self.use_for_single_location and self.graded:
max_grade = self.max_grade max_grade = self.weight
return max_grade return max_grade
def get_next_submission(self, data): def get_next_submission(self, data):
...@@ -530,7 +533,7 @@ class PeerGradingModule(PeerGradingFields, XModule): ...@@ -530,7 +533,7 @@ class PeerGradingModule(PeerGradingFields, XModule):
problem_location = problem['location'] problem_location = problem['location']
descriptor = _find_corresponding_module_for_location(problem_location) descriptor = _find_corresponding_module_for_location(problem_location)
if descriptor: if descriptor:
problem['due'] = descriptor._model_data.get('peer_grading_due', None) problem['due'] = descriptor._model_data.get('due', None)
grace_period_string = descriptor._model_data.get('graceperiod', None) grace_period_string = descriptor._model_data.get('graceperiod', None)
try: try:
problem_timeinfo = TimeInfo(problem['due'], grace_period_string) problem_timeinfo = TimeInfo(problem['due'], grace_period_string)
...@@ -617,9 +620,14 @@ class PeerGradingDescriptor(PeerGradingFields, RawDescriptor): ...@@ -617,9 +620,14 @@ class PeerGradingDescriptor(PeerGradingFields, RawDescriptor):
#Specify whether or not to pass in open ended interface #Specify whether or not to pass in open ended interface
needs_open_ended_interface = True needs_open_ended_interface = True
metadata_translations = {
'is_graded': 'graded',
'attempts': 'max_attempts',
'due_data' : 'due'
}
@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
non_editable_fields.extend([PeerGradingFields.due_date, PeerGradingFields.grace_period_string, non_editable_fields.extend([PeerGradingFields.due, PeerGradingFields.grace_period_string])
PeerGradingFields.max_grade])
return non_editable_fields return non_editable_fields
...@@ -335,7 +335,7 @@ class CombinedOpenEndedModuleTest(unittest.TestCase): ...@@ -335,7 +335,7 @@ class CombinedOpenEndedModuleTest(unittest.TestCase):
's3_interface': test_util_open_ended.S3_INTERFACE, 's3_interface': test_util_open_ended.S3_INTERFACE,
'open_ended_grading_interface': test_util_open_ended.OPEN_ENDED_GRADING_INTERFACE, 'open_ended_grading_interface': test_util_open_ended.OPEN_ENDED_GRADING_INTERFACE,
'skip_basic_checks': False, 'skip_basic_checks': False,
'is_graded': True, 'graded': True,
} }
oeparam = etree.XML(''' oeparam = etree.XML('''
...@@ -453,7 +453,7 @@ class CombinedOpenEndedModuleTest(unittest.TestCase): ...@@ -453,7 +453,7 @@ class CombinedOpenEndedModuleTest(unittest.TestCase):
self.assertFalse(changed) self.assertFalse(changed)
def test_get_score_realistic(self): def test_get_score_realistic(self):
instance_state = r"""{"ready_to_reset": false, "skip_spelling_checks": true, "current_task_number": 1, "weight": 5.0, "graceperiod": "1 day 12 hours 59 minutes 59 seconds", "is_graded": "True", "task_states": ["{\"child_created\": false, \"child_attempts\": 4, \"version\": 1, \"child_history\": [{\"answer\": \"The students\\u2019 data are recorded in the table below.\\r\\n\\r\\nStarting Mass (g)\\tEnding Mass (g)\\tDifference in Mass (g)\\r\\nMarble\\t 9.8\\t 9.4\\t\\u20130.4\\r\\nLimestone\\t10.4\\t 9.1\\t\\u20131.3\\r\\nWood\\t11.2\\t11.2\\t 0.0\\r\\nPlastic\\t 7.2\\t 7.1\\t\\u20130.1\\r\\nAfter reading the group\\u2019s procedure, describe what additional information you would need in order to replicate the expe\", \"post_assessment\": \"{\\\"submission_id\\\": 3097, \\\"score\\\": 0, \\\"feedback\\\": \\\"{\\\\\\\"spelling\\\\\\\": \\\\\\\"Spelling: Ok.\\\\\\\", \\\\\\\"grammar\\\\\\\": \\\\\\\"Grammar: More grammar errors than average.\\\\\\\", \\\\\\\"markup-text\\\\\\\": \\\\\\\"the students data are recorded in the <bg>table below . starting mass</bg> g ending mass g difference in mass g marble . . . limestone . . . wood . . . plastic . . . after reading the groups <bg>procedure , describe what additional</bg> information you would need in order to replicate the <bs>expe</bs>\\\\\\\"}\\\", \\\"success\\\": true, \\\"grader_id\\\": 3233, \\\"grader_type\\\": \\\"ML\\\", \\\"rubric_scores_complete\\\": true, \\\"rubric_xml\\\": \\\"<rubric><category><description>Response Quality</description><score>0</score><option points='0'>The response is not a satisfactory answer to the question. It either fails to address the question or does so in a limited way, with no evidence of higher-order thinking.</option><option points='1'>The response is a marginal answer to the question. It may contain some elements of a proficient response, but it is inaccurate or incomplete.</option><option points='2'>The response is a proficient answer to the question. It is generally correct, although it may contain minor inaccuracies. There is limited evidence of higher-order thinking.</option><option points='3'>The response is correct, complete, and contains evidence of higher-order thinking.</option></category></rubric>\\\"}\", \"score\": 0}, {\"answer\": \"After 24 hours, remove the samples from the containers and rinse each sample with distilled water.\\r\\nAllow the samples to sit and dry for 30 minutes.\\r\\nDetermine the mass of each sample.\\r\\nThe students\\u2019 data are recorded in the table below.\\r\\n\\r\\nStarting Mass (g)\\tEnding Mass (g)\\tDifference in Mass (g)\\r\\nMarble\\t 9.8\\t 9.4\\t\\u20130.4\\r\\nLimestone\\t10.4\\t 9.1\\t\\u20131.3\\r\\nWood\\t11.2\\t11.2\\t 0.0\\r\\nPlastic\\t 7.2\\t 7.1\\t\\u20130.1\\r\\nAfter reading the\", \"post_assessment\": \"[3]\", \"score\": 3}, {\"answer\": \"To replicate the experiment, the procedure would require more detail. One piece of information that is omitted is the amount of vinegar used in the experiment. It is also important to know what temperature the experiment was kept at during the 24 hours. Finally, the procedure needs to include details about the experiment, for example if the whole sample must be submerged.\", \"post_assessment\": \"[3]\", \"score\": 3}, {\"answer\": \"e the mass of four different samples.\\r\\nPour vinegar in each of four separate, but identical, containers.\\r\\nPlace a sample of one material into one container and label. Repeat with remaining samples, placing a single sample into a single container.\\r\\nAfter 24 hours, remove the samples from the containers and rinse each sample with distilled water.\\r\\nAllow the samples to sit and dry for 30 minutes.\\r\\nDetermine the mass of each sample.\\r\\nThe students\\u2019 data are recorded in the table below.\\r\\n\", \"post_assessment\": \"[3]\", \"score\": 3}, {\"answer\": \"\", \"post_assessment\": \"[3]\", \"score\": 3}], \"max_score\": 3, \"child_state\": \"done\"}", "{\"child_created\": false, \"child_attempts\": 0, \"version\": 1, \"child_history\": [{\"answer\": \"The students\\u2019 data are recorded in the table below.\\r\\n\\r\\nStarting Mass (g)\\tEnding Mass (g)\\tDifference in Mass (g)\\r\\nMarble\\t 9.8\\t 9.4\\t\\u20130.4\\r\\nLimestone\\t10.4\\t 9.1\\t\\u20131.3\\r\\nWood\\t11.2\\t11.2\\t 0.0\\r\\nPlastic\\t 7.2\\t 7.1\\t\\u20130.1\\r\\nAfter reading the group\\u2019s procedure, describe what additional information you would need in order to replicate the expe\", \"post_assessment\": \"{\\\"submission_id\\\": 3097, \\\"score\\\": 0, \\\"feedback\\\": \\\"{\\\\\\\"spelling\\\\\\\": \\\\\\\"Spelling: Ok.\\\\\\\", \\\\\\\"grammar\\\\\\\": \\\\\\\"Grammar: More grammar errors than average.\\\\\\\", \\\\\\\"markup-text\\\\\\\": \\\\\\\"the students data are recorded in the <bg>table below . starting mass</bg> g ending mass g difference in mass g marble . . . limestone . . . wood . . . plastic . . . after reading the groups <bg>procedure , describe what additional</bg> information you would need in order to replicate the <bs>expe</bs>\\\\\\\"}\\\", \\\"success\\\": true, \\\"grader_id\\\": 3233, \\\"grader_type\\\": \\\"ML\\\", \\\"rubric_scores_complete\\\": true, \\\"rubric_xml\\\": \\\"<rubric><category><description>Response Quality</description><score>0</score><option points='0'>The response is not a satisfactory answer to the question. It either fails to address the question or does so in a limited way, with no evidence of higher-order thinking.</option><option points='1'>The response is a marginal answer to the question. It may contain some elements of a proficient response, but it is inaccurate or incomplete.</option><option points='2'>The response is a proficient answer to the question. It is generally correct, although it may contain minor inaccuracies. There is limited evidence of higher-order thinking.</option><option points='3'>The response is correct, complete, and contains evidence of higher-order thinking.</option></category></rubric>\\\"}\", \"score\": 0}, {\"answer\": \"After 24 hours, remove the samples from the containers and rinse each sample with distilled water.\\r\\nAllow the samples to sit and dry for 30 minutes.\\r\\nDetermine the mass of each sample.\\r\\nThe students\\u2019 data are recorded in the table below.\\r\\n\\r\\nStarting Mass (g)\\tEnding Mass (g)\\tDifference in Mass (g)\\r\\nMarble\\t 9.8\\t 9.4\\t\\u20130.4\\r\\nLimestone\\t10.4\\t 9.1\\t\\u20131.3\\r\\nWood\\t11.2\\t11.2\\t 0.0\\r\\nPlastic\\t 7.2\\t 7.1\\t\\u20130.1\\r\\nAfter reading the\", \"post_assessment\": \"{\\\"submission_id\\\": 3098, \\\"score\\\": 0, \\\"feedback\\\": \\\"{\\\\\\\"spelling\\\\\\\": \\\\\\\"Spelling: Ok.\\\\\\\", \\\\\\\"grammar\\\\\\\": \\\\\\\"Grammar: Ok.\\\\\\\", \\\\\\\"markup-text\\\\\\\": \\\\\\\"after hours , remove the samples from the containers and rinse each sample with distilled water . allow the samples to sit and dry for minutes . determine the mass of each sample . the students data are recorded in the <bg>table below . starting mass</bg> g ending mass g difference in mass g marble . . . limestone . . . wood . . . plastic . . . after reading the\\\\\\\"}\\\", \\\"success\\\": true, \\\"grader_id\\\": 3235, \\\"grader_type\\\": \\\"ML\\\", \\\"rubric_scores_complete\\\": true, \\\"rubric_xml\\\": \\\"<rubric><category><description>Response Quality</description><score>0</score><option points='0'>The response is not a satisfactory answer to the question. It either fails to address the question or does so in a limited way, with no evidence of higher-order thinking.</option><option points='1'>The response is a marginal answer to the question. It may contain some elements of a proficient response, but it is inaccurate or incomplete.</option><option points='2'>The response is a proficient answer to the question. It is generally correct, although it may contain minor inaccuracies. There is limited evidence of higher-order thinking.</option><option points='3'>The response is correct, complete, and contains evidence of higher-order thinking.</option></category></rubric>\\\"}\", \"score\": 0}, {\"answer\": \"To replicate the experiment, the procedure would require more detail. One piece of information that is omitted is the amount of vinegar used in the experiment. It is also important to know what temperature the experiment was kept at during the 24 hours. Finally, the procedure needs to include details about the experiment, for example if the whole sample must be submerged.\", \"post_assessment\": \"{\\\"submission_id\\\": 3099, \\\"score\\\": 3, \\\"feedback\\\": \\\"{\\\\\\\"spelling\\\\\\\": \\\\\\\"Spelling: Ok.\\\\\\\", \\\\\\\"grammar\\\\\\\": \\\\\\\"Grammar: Ok.\\\\\\\", \\\\\\\"markup-text\\\\\\\": \\\\\\\"to replicate the experiment , the procedure would require <bg>more detail . one</bg> piece of information <bg>that is omitted is the</bg> amount of vinegar used in the experiment . it is also important to know what temperature the experiment was kept at during the hours . finally , the procedure needs to include details about the experiment , for example if the whole sample must be submerged .\\\\\\\"}\\\", \\\"success\\\": true, \\\"grader_id\\\": 3237, \\\"grader_type\\\": \\\"ML\\\", \\\"rubric_scores_complete\\\": true, \\\"rubric_xml\\\": \\\"<rubric><category><description>Response Quality</description><score>3</score><option points='0'>The response is not a satisfactory answer to the question. It either fails to address the question or does so in a limited way, with no evidence of higher-order thinking.</option><option points='1'>The response is a marginal answer to the question. It may contain some elements of a proficient response, but it is inaccurate or incomplete.</option><option points='2'>The response is a proficient answer to the question. It is generally correct, although it may contain minor inaccuracies. There is limited evidence of higher-order thinking.</option><option points='3'>The response is correct, complete, and contains evidence of higher-order thinking.</option></category></rubric>\\\"}\", \"score\": 3}, {\"answer\": \"e the mass of four different samples.\\r\\nPour vinegar in each of four separate, but identical, containers.\\r\\nPlace a sample of one material into one container and label. Repeat with remaining samples, placing a single sample into a single container.\\r\\nAfter 24 hours, remove the samples from the containers and rinse each sample with distilled water.\\r\\nAllow the samples to sit and dry for 30 minutes.\\r\\nDetermine the mass of each sample.\\r\\nThe students\\u2019 data are recorded in the table below.\\r\\n\", \"post_assessment\": \"{\\\"submission_id\\\": 3100, \\\"score\\\": 0, \\\"feedback\\\": \\\"{\\\\\\\"spelling\\\\\\\": \\\\\\\"Spelling: Ok.\\\\\\\", \\\\\\\"grammar\\\\\\\": \\\\\\\"Grammar: Ok.\\\\\\\", \\\\\\\"markup-text\\\\\\\": \\\\\\\"e the mass of four different samples . pour vinegar in <bg>each of four separate</bg> , but identical , containers . place a sample of one material into one container and label . repeat with remaining samples , placing a single sample into a single container . after hours , remove the samples from the containers and rinse each sample with distilled water . allow the samples to sit and dry for minutes . determine the mass of each sample . the students data are recorded in the table below . \\\\\\\"}\\\", \\\"success\\\": true, \\\"grader_id\\\": 3239, \\\"grader_type\\\": \\\"ML\\\", \\\"rubric_scores_complete\\\": true, \\\"rubric_xml\\\": \\\"<rubric><category><description>Response Quality</description><score>0</score><option points='0'>The response is not a satisfactory answer to the question. It either fails to address the question or does so in a limited way, with no evidence of higher-order thinking.</option><option points='1'>The response is a marginal answer to the question. It may contain some elements of a proficient response, but it is inaccurate or incomplete.</option><option points='2'>The response is a proficient answer to the question. It is generally correct, although it may contain minor inaccuracies. There is limited evidence of higher-order thinking.</option><option points='3'>The response is correct, complete, and contains evidence of higher-order thinking.</option></category></rubric>\\\"}\", \"score\": 0}, {\"answer\": \"\", \"post_assessment\": \"{\\\"submission_id\\\": 3101, \\\"score\\\": 0, \\\"feedback\\\": \\\"{\\\\\\\"spelling\\\\\\\": \\\\\\\"Spelling: Ok.\\\\\\\", \\\\\\\"grammar\\\\\\\": \\\\\\\"Grammar: Ok.\\\\\\\", \\\\\\\"markup-text\\\\\\\": \\\\\\\"invalid essay .\\\\\\\"}\\\", \\\"success\\\": true, \\\"grader_id\\\": 3241, \\\"grader_type\\\": \\\"ML\\\", \\\"rubric_scores_complete\\\": true, \\\"rubric_xml\\\": \\\"<rubric><category><description>Response Quality</description><score>0</score><option points='0'>The response is not a satisfactory answer to the question. It either fails to address the question or does so in a limited way, with no evidence of higher-order thinking.</option><option points='1'>The response is a marginal answer to the question. It may contain some elements of a proficient response, but it is inaccurate or incomplete.</option><option points='2'>The response is a proficient answer to the question. It is generally correct, although it may contain minor inaccuracies. There is limited evidence of higher-order thinking.</option><option points='3'>The response is correct, complete, and contains evidence of higher-order thinking.</option></category></rubric>\\\"}\", \"score\": 0}], \"max_score\": 3, \"child_state\": \"done\"}"], "attempts": "10000", "student_attempts": 0, "due": null, "state": "done", "accept_file_upload": false, "display_name": "Science Question -- Machine Assessed"}""" instance_state = r"""{"ready_to_reset": false, "skip_spelling_checks": true, "current_task_number": 1, "weight": 5.0, "graceperiod": "1 day 12 hours 59 minutes 59 seconds", "graded": "True", "task_states": ["{\"child_created\": false, \"child_attempts\": 4, \"version\": 1, \"child_history\": [{\"answer\": \"The students\\u2019 data are recorded in the table below.\\r\\n\\r\\nStarting Mass (g)\\tEnding Mass (g)\\tDifference in Mass (g)\\r\\nMarble\\t 9.8\\t 9.4\\t\\u20130.4\\r\\nLimestone\\t10.4\\t 9.1\\t\\u20131.3\\r\\nWood\\t11.2\\t11.2\\t 0.0\\r\\nPlastic\\t 7.2\\t 7.1\\t\\u20130.1\\r\\nAfter reading the group\\u2019s procedure, describe what additional information you would need in order to replicate the expe\", \"post_assessment\": \"{\\\"submission_id\\\": 3097, \\\"score\\\": 0, \\\"feedback\\\": \\\"{\\\\\\\"spelling\\\\\\\": \\\\\\\"Spelling: Ok.\\\\\\\", \\\\\\\"grammar\\\\\\\": \\\\\\\"Grammar: More grammar errors than average.\\\\\\\", \\\\\\\"markup-text\\\\\\\": \\\\\\\"the students data are recorded in the <bg>table below . starting mass</bg> g ending mass g difference in mass g marble . . . limestone . . . wood . . . plastic . . . after reading the groups <bg>procedure , describe what additional</bg> information you would need in order to replicate the <bs>expe</bs>\\\\\\\"}\\\", \\\"success\\\": true, \\\"grader_id\\\": 3233, \\\"grader_type\\\": \\\"ML\\\", \\\"rubric_scores_complete\\\": true, \\\"rubric_xml\\\": \\\"<rubric><category><description>Response Quality</description><score>0</score><option points='0'>The response is not a satisfactory answer to the question. It either fails to address the question or does so in a limited way, with no evidence of higher-order thinking.</option><option points='1'>The response is a marginal answer to the question. It may contain some elements of a proficient response, but it is inaccurate or incomplete.</option><option points='2'>The response is a proficient answer to the question. It is generally correct, although it may contain minor inaccuracies. There is limited evidence of higher-order thinking.</option><option points='3'>The response is correct, complete, and contains evidence of higher-order thinking.</option></category></rubric>\\\"}\", \"score\": 0}, {\"answer\": \"After 24 hours, remove the samples from the containers and rinse each sample with distilled water.\\r\\nAllow the samples to sit and dry for 30 minutes.\\r\\nDetermine the mass of each sample.\\r\\nThe students\\u2019 data are recorded in the table below.\\r\\n\\r\\nStarting Mass (g)\\tEnding Mass (g)\\tDifference in Mass (g)\\r\\nMarble\\t 9.8\\t 9.4\\t\\u20130.4\\r\\nLimestone\\t10.4\\t 9.1\\t\\u20131.3\\r\\nWood\\t11.2\\t11.2\\t 0.0\\r\\nPlastic\\t 7.2\\t 7.1\\t\\u20130.1\\r\\nAfter reading the\", \"post_assessment\": \"[3]\", \"score\": 3}, {\"answer\": \"To replicate the experiment, the procedure would require more detail. One piece of information that is omitted is the amount of vinegar used in the experiment. It is also important to know what temperature the experiment was kept at during the 24 hours. Finally, the procedure needs to include details about the experiment, for example if the whole sample must be submerged.\", \"post_assessment\": \"[3]\", \"score\": 3}, {\"answer\": \"e the mass of four different samples.\\r\\nPour vinegar in each of four separate, but identical, containers.\\r\\nPlace a sample of one material into one container and label. Repeat with remaining samples, placing a single sample into a single container.\\r\\nAfter 24 hours, remove the samples from the containers and rinse each sample with distilled water.\\r\\nAllow the samples to sit and dry for 30 minutes.\\r\\nDetermine the mass of each sample.\\r\\nThe students\\u2019 data are recorded in the table below.\\r\\n\", \"post_assessment\": \"[3]\", \"score\": 3}, {\"answer\": \"\", \"post_assessment\": \"[3]\", \"score\": 3}], \"max_score\": 3, \"child_state\": \"done\"}", "{\"child_created\": false, \"child_attempts\": 0, \"version\": 1, \"child_history\": [{\"answer\": \"The students\\u2019 data are recorded in the table below.\\r\\n\\r\\nStarting Mass (g)\\tEnding Mass (g)\\tDifference in Mass (g)\\r\\nMarble\\t 9.8\\t 9.4\\t\\u20130.4\\r\\nLimestone\\t10.4\\t 9.1\\t\\u20131.3\\r\\nWood\\t11.2\\t11.2\\t 0.0\\r\\nPlastic\\t 7.2\\t 7.1\\t\\u20130.1\\r\\nAfter reading the group\\u2019s procedure, describe what additional information you would need in order to replicate the expe\", \"post_assessment\": \"{\\\"submission_id\\\": 3097, \\\"score\\\": 0, \\\"feedback\\\": \\\"{\\\\\\\"spelling\\\\\\\": \\\\\\\"Spelling: Ok.\\\\\\\", \\\\\\\"grammar\\\\\\\": \\\\\\\"Grammar: More grammar errors than average.\\\\\\\", \\\\\\\"markup-text\\\\\\\": \\\\\\\"the students data are recorded in the <bg>table below . starting mass</bg> g ending mass g difference in mass g marble . . . limestone . . . wood . . . plastic . . . after reading the groups <bg>procedure , describe what additional</bg> information you would need in order to replicate the <bs>expe</bs>\\\\\\\"}\\\", \\\"success\\\": true, \\\"grader_id\\\": 3233, \\\"grader_type\\\": \\\"ML\\\", \\\"rubric_scores_complete\\\": true, \\\"rubric_xml\\\": \\\"<rubric><category><description>Response Quality</description><score>0</score><option points='0'>The response is not a satisfactory answer to the question. It either fails to address the question or does so in a limited way, with no evidence of higher-order thinking.</option><option points='1'>The response is a marginal answer to the question. It may contain some elements of a proficient response, but it is inaccurate or incomplete.</option><option points='2'>The response is a proficient answer to the question. It is generally correct, although it may contain minor inaccuracies. There is limited evidence of higher-order thinking.</option><option points='3'>The response is correct, complete, and contains evidence of higher-order thinking.</option></category></rubric>\\\"}\", \"score\": 0}, {\"answer\": \"After 24 hours, remove the samples from the containers and rinse each sample with distilled water.\\r\\nAllow the samples to sit and dry for 30 minutes.\\r\\nDetermine the mass of each sample.\\r\\nThe students\\u2019 data are recorded in the table below.\\r\\n\\r\\nStarting Mass (g)\\tEnding Mass (g)\\tDifference in Mass (g)\\r\\nMarble\\t 9.8\\t 9.4\\t\\u20130.4\\r\\nLimestone\\t10.4\\t 9.1\\t\\u20131.3\\r\\nWood\\t11.2\\t11.2\\t 0.0\\r\\nPlastic\\t 7.2\\t 7.1\\t\\u20130.1\\r\\nAfter reading the\", \"post_assessment\": \"{\\\"submission_id\\\": 3098, \\\"score\\\": 0, \\\"feedback\\\": \\\"{\\\\\\\"spelling\\\\\\\": \\\\\\\"Spelling: Ok.\\\\\\\", \\\\\\\"grammar\\\\\\\": \\\\\\\"Grammar: Ok.\\\\\\\", \\\\\\\"markup-text\\\\\\\": \\\\\\\"after hours , remove the samples from the containers and rinse each sample with distilled water . allow the samples to sit and dry for minutes . determine the mass of each sample . the students data are recorded in the <bg>table below . starting mass</bg> g ending mass g difference in mass g marble . . . limestone . . . wood . . . plastic . . . after reading the\\\\\\\"}\\\", \\\"success\\\": true, \\\"grader_id\\\": 3235, \\\"grader_type\\\": \\\"ML\\\", \\\"rubric_scores_complete\\\": true, \\\"rubric_xml\\\": \\\"<rubric><category><description>Response Quality</description><score>0</score><option points='0'>The response is not a satisfactory answer to the question. It either fails to address the question or does so in a limited way, with no evidence of higher-order thinking.</option><option points='1'>The response is a marginal answer to the question. It may contain some elements of a proficient response, but it is inaccurate or incomplete.</option><option points='2'>The response is a proficient answer to the question. It is generally correct, although it may contain minor inaccuracies. There is limited evidence of higher-order thinking.</option><option points='3'>The response is correct, complete, and contains evidence of higher-order thinking.</option></category></rubric>\\\"}\", \"score\": 0}, {\"answer\": \"To replicate the experiment, the procedure would require more detail. One piece of information that is omitted is the amount of vinegar used in the experiment. It is also important to know what temperature the experiment was kept at during the 24 hours. Finally, the procedure needs to include details about the experiment, for example if the whole sample must be submerged.\", \"post_assessment\": \"{\\\"submission_id\\\": 3099, \\\"score\\\": 3, \\\"feedback\\\": \\\"{\\\\\\\"spelling\\\\\\\": \\\\\\\"Spelling: Ok.\\\\\\\", \\\\\\\"grammar\\\\\\\": \\\\\\\"Grammar: Ok.\\\\\\\", \\\\\\\"markup-text\\\\\\\": \\\\\\\"to replicate the experiment , the procedure would require <bg>more detail . one</bg> piece of information <bg>that is omitted is the</bg> amount of vinegar used in the experiment . it is also important to know what temperature the experiment was kept at during the hours . finally , the procedure needs to include details about the experiment , for example if the whole sample must be submerged .\\\\\\\"}\\\", \\\"success\\\": true, \\\"grader_id\\\": 3237, \\\"grader_type\\\": \\\"ML\\\", \\\"rubric_scores_complete\\\": true, \\\"rubric_xml\\\": \\\"<rubric><category><description>Response Quality</description><score>3</score><option points='0'>The response is not a satisfactory answer to the question. It either fails to address the question or does so in a limited way, with no evidence of higher-order thinking.</option><option points='1'>The response is a marginal answer to the question. It may contain some elements of a proficient response, but it is inaccurate or incomplete.</option><option points='2'>The response is a proficient answer to the question. It is generally correct, although it may contain minor inaccuracies. There is limited evidence of higher-order thinking.</option><option points='3'>The response is correct, complete, and contains evidence of higher-order thinking.</option></category></rubric>\\\"}\", \"score\": 3}, {\"answer\": \"e the mass of four different samples.\\r\\nPour vinegar in each of four separate, but identical, containers.\\r\\nPlace a sample of one material into one container and label. Repeat with remaining samples, placing a single sample into a single container.\\r\\nAfter 24 hours, remove the samples from the containers and rinse each sample with distilled water.\\r\\nAllow the samples to sit and dry for 30 minutes.\\r\\nDetermine the mass of each sample.\\r\\nThe students\\u2019 data are recorded in the table below.\\r\\n\", \"post_assessment\": \"{\\\"submission_id\\\": 3100, \\\"score\\\": 0, \\\"feedback\\\": \\\"{\\\\\\\"spelling\\\\\\\": \\\\\\\"Spelling: Ok.\\\\\\\", \\\\\\\"grammar\\\\\\\": \\\\\\\"Grammar: Ok.\\\\\\\", \\\\\\\"markup-text\\\\\\\": \\\\\\\"e the mass of four different samples . pour vinegar in <bg>each of four separate</bg> , but identical , containers . place a sample of one material into one container and label . repeat with remaining samples , placing a single sample into a single container . after hours , remove the samples from the containers and rinse each sample with distilled water . allow the samples to sit and dry for minutes . determine the mass of each sample . the students data are recorded in the table below . \\\\\\\"}\\\", \\\"success\\\": true, \\\"grader_id\\\": 3239, \\\"grader_type\\\": \\\"ML\\\", \\\"rubric_scores_complete\\\": true, \\\"rubric_xml\\\": \\\"<rubric><category><description>Response Quality</description><score>0</score><option points='0'>The response is not a satisfactory answer to the question. It either fails to address the question or does so in a limited way, with no evidence of higher-order thinking.</option><option points='1'>The response is a marginal answer to the question. It may contain some elements of a proficient response, but it is inaccurate or incomplete.</option><option points='2'>The response is a proficient answer to the question. It is generally correct, although it may contain minor inaccuracies. There is limited evidence of higher-order thinking.</option><option points='3'>The response is correct, complete, and contains evidence of higher-order thinking.</option></category></rubric>\\\"}\", \"score\": 0}, {\"answer\": \"\", \"post_assessment\": \"{\\\"submission_id\\\": 3101, \\\"score\\\": 0, \\\"feedback\\\": \\\"{\\\\\\\"spelling\\\\\\\": \\\\\\\"Spelling: Ok.\\\\\\\", \\\\\\\"grammar\\\\\\\": \\\\\\\"Grammar: Ok.\\\\\\\", \\\\\\\"markup-text\\\\\\\": \\\\\\\"invalid essay .\\\\\\\"}\\\", \\\"success\\\": true, \\\"grader_id\\\": 3241, \\\"grader_type\\\": \\\"ML\\\", \\\"rubric_scores_complete\\\": true, \\\"rubric_xml\\\": \\\"<rubric><category><description>Response Quality</description><score>0</score><option points='0'>The response is not a satisfactory answer to the question. It either fails to address the question or does so in a limited way, with no evidence of higher-order thinking.</option><option points='1'>The response is a marginal answer to the question. It may contain some elements of a proficient response, but it is inaccurate or incomplete.</option><option points='2'>The response is a proficient answer to the question. It is generally correct, although it may contain minor inaccuracies. There is limited evidence of higher-order thinking.</option><option points='3'>The response is correct, complete, and contains evidence of higher-order thinking.</option></category></rubric>\\\"}\", \"score\": 0}], \"max_score\": 3, \"child_state\": \"done\"}"], "attempts": "10000", "student_attempts": 0, "due": null, "state": "done", "accept_file_upload": false, "display_name": "Science Question -- Machine Assessed"}"""
instance_state = json.loads(instance_state) instance_state = json.loads(instance_state)
rubric = """ rubric = """
<rubric> <rubric>
...@@ -631,3 +631,58 @@ class OpenEndedModuleXmlTest(unittest.TestCase, DummyModulestore): ...@@ -631,3 +631,58 @@ class OpenEndedModuleXmlTest(unittest.TestCase, DummyModulestore):
#reset the problem #reset the problem
module.handle_ajax("reset", {}) module.handle_ajax("reset", {})
self.assertEqual(module.state, "initial") self.assertEqual(module.state, "initial")
class OpenEndedModuleXmlAttemptTest(unittest.TestCase, DummyModulestore):
"""
Test if student is able to reset the problem
"""
problem_location = Location(["i4x", "edX", "open_ended", "combinedopenended", "SampleQuestion1Attempt"])
answer = "blah blah"
assessment = [0, 1]
hint = "blah"
def setUp(self):
self.test_system = get_test_system()
self.test_system.xqueue['interface'] = Mock(
send_to_queue=Mock(side_effect=[1, "queued"])
)
self.setup_modulestore(COURSE)
def test_reset_fail(self):
"""
Test the flow of the module if we complete the self assessment step and then reset
Since the problem only allows one attempt, should fail.
@return:
"""
assessment = [0, 1]
module = self.get_module_from_location(self.problem_location, COURSE)
#Simulate a student saving an answer
module.handle_ajax("save_answer", {"student_answer": self.answer})
status = module.handle_ajax("get_status", {})
self.assertTrue(isinstance(status, basestring))
#Mock a student submitting an assessment
assessment_dict = MockQueryDict()
assessment_dict.update({'assessment': sum(assessment), 'score_list[]': assessment})
module.handle_ajax("save_assessment", assessment_dict)
task_one_json = json.loads(module.task_states[0])
self.assertEqual(json.loads(task_one_json['child_history'][0]['post_assessment']), assessment)
status = module.handle_ajax("get_status", {})
self.assertTrue(isinstance(status, basestring))
#Move to the next step in the problem
module.handle_ajax("next_problem", {})
self.assertEqual(module.current_task_number, 0)
html = module.get_html()
self.assertTrue(isinstance(html, basestring))
#Module should now be done
rubric = module.handle_ajax("get_combined_rubric", {})
self.assertTrue(isinstance(rubric, basestring))
self.assertEqual(module.state, "done")
#Try to reset, should fail because only 1 attempt is allowed
reset_data = json.loads(module.handle_ajax("reset", {}))
self.assertEqual(reset_data['success'], False)
<combinedopenended attempts="1" display_name = "Humanities Question -- Machine Assessed">
<rubric>
<rubric>
<category>
<description>Writing Applications</description>
<option> The essay loses focus, has little information or supporting details, and the organization makes it difficult to follow.</option>
<option> The essay presents a mostly unified theme, includes sufficient information to convey the theme, and is generally organized well.</option>
</category>
<category>
<description> Language Conventions </description>
<option> The essay demonstrates a reasonable command of proper spelling and grammar. </option>
<option> The essay demonstrates superior command of proper spelling and grammar.</option>
</category>
</rubric>
</rubric>
<prompt>
<h4>Censorship in the Libraries</h4>
<p>"All of us can think of a book that we hope none of our children or any other children have taken off the shelf. But if I have the right to remove that book from the shelf -- that work I abhor -- then you also have exactly the same right and so does everyone else. And then we have no books left on the shelf for any of us." --Katherine Paterson, Author</p>
<p>Write a persuasive essay to a newspaper reflecting your vies on censorship in libraries. Do you believe that certain materials, such as books, music, movies, magazines, etc., should be removed from the shelves if they are found offensive? Support your position with convincing arguments from your own experience, observations, and/or reading.</p>
</prompt>
<task>
<selfassessment/>
</task>
</combinedopenended>
\ No newline at end of file
<course> <course>
<chapter url_name="Overview"> <chapter url_name="Overview">
<combinedopenended url_name="SampleQuestion"/> <combinedopenended url_name="SampleQuestion"/>
<combinedopenended url_name="SampleQuestion1Attempt"/>
<peergrading url_name="PeerGradingSample"/> <peergrading url_name="PeerGradingSample"/>
<peergrading url_name="PeerGradingScored"/> <peergrading url_name="PeerGradingScored"/>
</chapter> </chapter>
......
...@@ -15,6 +15,7 @@ from xmodule.course_module import CourseDescriptor ...@@ -15,6 +15,7 @@ from xmodule.course_module import CourseDescriptor
from student.models import unique_id_for_user from student.models import unique_id_for_user
from xmodule.x_module import ModuleSystem from xmodule.x_module import ModuleSystem
from mitxmako.shortcuts import render_to_string from mitxmako.shortcuts import render_to_string
from utils import does_location_exist
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
...@@ -240,7 +241,6 @@ def get_next(request, course_id): ...@@ -240,7 +241,6 @@ def get_next(request, course_id):
return HttpResponse(_get_next(course_id, grader_id, location), return HttpResponse(_get_next(course_id, grader_id, location),
mimetype="application/json") mimetype="application/json")
def get_problem_list(request, course_id): def get_problem_list(request, course_id):
""" """
Get all the problems for the given course id Get all the problems for the given course id
...@@ -266,6 +266,20 @@ def get_problem_list(request, course_id): ...@@ -266,6 +266,20 @@ def get_problem_list(request, course_id):
_check_access(request.user, course_id) _check_access(request.user, course_id)
try: try:
response = staff_grading_service().get_problem_list(course_id, unique_id_for_user(request.user)) response = staff_grading_service().get_problem_list(course_id, unique_id_for_user(request.user))
response = json.loads(response)
problem_list = response['problem_list']
valid_problem_list = []
for i in xrange(0,len(problem_list)):
#Needed to ensure that the 'location' key can be accessed
try:
problem_list[i] = json.loads(problem_list[i])
except Exception:
pass
if does_location_exist(course_id, problem_list[i]['location']):
valid_problem_list.append(problem_list[i])
response['problem_list'] = valid_problem_list
response = json.dumps(response)
return HttpResponse(response, return HttpResponse(response,
mimetype="application/json") mimetype="application/json")
except GradingServiceError: except GradingServiceError:
......
from xmodule.modulestore import search
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.exceptions import ItemNotFoundError
def does_location_exist(course_id, location):
"""
Checks to see if a valid module exists at a given location (ie has not been deleted)
course_id - string course id
location - string location
"""
try:
search.path_to_location(modulestore(), course_id, location)
return True
except ItemNotFoundError:
#If the problem cannot be found at the location received from the grading controller server, it has been deleted by the course author.
return False
...@@ -178,6 +178,7 @@ def student_problem_list(request, course_id): ...@@ -178,6 +178,7 @@ def student_problem_list(request, course_id):
error_text = "" error_text = ""
problem_list = [] problem_list = []
base_course_url = reverse('courses') base_course_url = reverse('courses')
list_to_remove = []
try: try:
#Get list of all open ended problems that the grading server knows about #Get list of all open ended problems that the grading server knows about
...@@ -191,7 +192,6 @@ def student_problem_list(request, course_id): ...@@ -191,7 +192,6 @@ def student_problem_list(request, course_id):
problem_list = problem_list_dict['problem_list'] problem_list = problem_list_dict['problem_list']
#A list of problems to remove (problems that can't be found in the course) #A list of problems to remove (problems that can't be found in the course)
list_to_remove = []
for i in xrange(0, len(problem_list)): for i in xrange(0, len(problem_list)):
try: try:
#Try to load each problem in the courseware to get links to them #Try to load each problem in the courseware to get links to them
......
...@@ -10,10 +10,6 @@ ...@@ -10,10 +10,6 @@
<div class="grader-status"> <div class="grader-status">
% if state == 'initial': % if state == 'initial':
<span class="unanswered" style="display:inline-block;" id="status_${id}">Unanswered</span> <span class="unanswered" style="display:inline-block;" id="status_${id}">Unanswered</span>
% elif state in ['done', 'post_assessment'] and correct == 'correct':
<span class="correct" id="status_${id}"></span> <p>Correct</p>
% elif state in ['done', 'post_assessment'] and correct == 'incorrect':
<span class="incorrect" id="status_${id}"></span> <p>Incorrect. </p>
% elif state == 'assessing': % elif state == 'assessing':
<span class="grading" id="status_${id}">Submitted for grading. <span class="grading" id="status_${id}">Submitted for grading.
% if eta_message is not None: % if eta_message is not None:
......
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