@@ -140,7 +140,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
|
||||
self.check_components_on_page(ADVANCED_COMPONENT_TYPES, ['Video Alpha',
|
||||
'Word cloud',
|
||||
'Annotation',
|
||||
'Open Ended Grading',
|
||||
'Open Response Assessment',
|
||||
'Peer Grading Interface'])
|
||||
|
||||
def test_advanced_components_require_two_clicks(self):
|
||||
|
||||
@@ -13,7 +13,7 @@ import textwrap
|
||||
|
||||
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"]
|
||||
|
||||
V1_STUDENT_ATTRIBUTES = ["current_task_number", "task_states", "state",
|
||||
@@ -29,36 +29,124 @@ VERSION_TUPLES = {
|
||||
|
||||
DEFAULT_VERSION = 1
|
||||
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>
|
||||
<category>
|
||||
<description>Category 1</description>
|
||||
<option>
|
||||
The response does not incorporate what is needed for a one response.
|
||||
</option>
|
||||
<option>
|
||||
The response is correct for category 1.
|
||||
</option>
|
||||
</category>
|
||||
</rubric>
|
||||
<category>
|
||||
<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>
|
||||
Includes sufficient information and supporting details. (Details may not be fully developed; ideas may be listed.) Explores some facets of the topic.
|
||||
</option>
|
||||
<option>
|
||||
Includes in-depth information and exceptional supporting details that are fully developed. Explores all facets of the topic.
|
||||
</option>
|
||||
</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>
|
||||
<prompt>
|
||||
<p>Why is the sky blue?</p>
|
||||
</prompt>
|
||||
<task>
|
||||
<selfassessment/>
|
||||
</task>
|
||||
<task>
|
||||
<openended min_score_to_attempt="1" max_score_to_attempt="2">
|
||||
<openendedparam>
|
||||
<initial_display>Enter essay here.</initial_display>
|
||||
<answer_display>This is the answer.</answer_display>
|
||||
<grader_payload>{"grader_settings" : "peer_grading.conf", "problem_id" : "700x/Demo"}</grader_payload>
|
||||
</openendedparam>
|
||||
</openended>
|
||||
</task>
|
||||
</combinedopenended>
|
||||
</rubric>
|
||||
|
||||
<task>
|
||||
<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>
|
||||
|
||||
<openended min_score_to_attempt="9" 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" : "peer_grading.conf", "problem_id" : "6.002x/Welcome/OETest"}</grader_payload>
|
||||
</openendedparam>
|
||||
</openended>
|
||||
</task>
|
||||
|
||||
</combinedopenended>
|
||||
""")
|
||||
|
||||
|
||||
@@ -84,35 +172,63 @@ class CombinedOpenEndedFields(object):
|
||||
display_name = String(
|
||||
display_name="Display Name",
|
||||
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
|
||||
)
|
||||
current_task_number = Integer(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)
|
||||
state = String(help="Which step within the current task that the student is on.", default="initial",
|
||||
scope=Scope.user_state)
|
||||
student_attempts = Integer(help="Number of attempts taken by the student on this problem", default=0,
|
||||
scope=Scope.user_state)
|
||||
ready_to_reset = Boolean(
|
||||
help="If the problem is ready to be reset or not.", default=False,
|
||||
current_task_number = Integer(
|
||||
help="Current task that the student is on.",
|
||||
default=0,
|
||||
scope=Scope.user_state
|
||||
)
|
||||
attempts = Integer(
|
||||
display_name="Maximum Attempts",
|
||||
help="The number of times the student can try to answer this problem.", default=1,
|
||||
scope=Scope.settings, values={"min" : 1 }
|
||||
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(
|
||||
help="If the problem is ready to be reset or not.",
|
||||
default=False,
|
||||
scope=Scope.user_state
|
||||
)
|
||||
max_attempts = Integer(
|
||||
display_name="Maximum Attempts",
|
||||
help="The number of times the student can try to answer this problem.",
|
||||
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(
|
||||
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(
|
||||
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.",
|
||||
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(
|
||||
help="Amount of time after the due date that submissions will be accepted",
|
||||
default=None,
|
||||
@@ -124,22 +240,51 @@ class CombinedOpenEndedFields(object):
|
||||
weight = Float(
|
||||
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.",
|
||||
scope=Scope.settings, values={"min" : 0 , "step": ".1"}
|
||||
scope=Scope.settings,
|
||||
values={"min" : 0 , "step": ".1"},
|
||||
default=1
|
||||
)
|
||||
markdown = String(
|
||||
help="Markdown source of this module",
|
||||
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]
|
||||
<p>Why is the sky blue?</p>
|
||||
[prompt]
|
||||
[tasks]
|
||||
(Self), ({1-2}AI)
|
||||
[tasks]
|
||||
[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]
|
||||
+ 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]
|
||||
(Self), ({4-12}AI), ({9-12}Peer)
|
||||
[tasks]
|
||||
|
||||
"""),
|
||||
scope=Scope.settings
|
||||
)
|
||||
@@ -194,37 +339,9 @@ class CombinedOpenEndedModule(CombinedOpenEndedFields, XModule):
|
||||
|
||||
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:
|
||||
<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>
|
||||
See DEFAULT_DATA for a sample.
|
||||
|
||||
"""
|
||||
XModule.__init__(self, *args, **kwargs)
|
||||
@@ -291,6 +408,7 @@ class CombinedOpenEndedDescriptor(CombinedOpenEndedFields, RawDescriptor):
|
||||
|
||||
has_score = True
|
||||
always_recalculate_grades = True
|
||||
template_dir_name = "combinedopenended"
|
||||
|
||||
#Specify whether or not to pass in S3 interface
|
||||
needs_s3_interface = True
|
||||
@@ -304,6 +422,11 @@ class CombinedOpenEndedDescriptor(CombinedOpenEndedFields, RawDescriptor):
|
||||
js_module_name = "OpenEndedMarkdownEditingDescriptor"
|
||||
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):
|
||||
_context = RawDescriptor.get_context(self)
|
||||
_context.update({'markdown': self.markdown,
|
||||
|
||||
@@ -50,6 +50,10 @@ Write a persuasive essay to a newspaper reflecting your vies on censorship in li
|
||||
mode: null
|
||||
})
|
||||
@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)
|
||||
@element.on('click', '.xml-tab', @onShowXMLButton)
|
||||
@element.on('click', '.format-buttons a', @onToolbarButton)
|
||||
|
||||
@@ -78,37 +78,7 @@ class CombinedOpenEndedV1Module():
|
||||
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:
|
||||
|
||||
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>
|
||||
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.
|
||||
|
||||
"""
|
||||
|
||||
@@ -131,14 +101,14 @@ class CombinedOpenEndedV1Module():
|
||||
|
||||
# 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.attempts = self.instance_state.get('attempts', MAX_ATTEMPTS)
|
||||
self.is_scored = self.instance_state.get('is_graded', IS_SCORED) in TRUE_DICT
|
||||
self.accept_file_upload = self.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.max_attempts = instance_state.get('max_attempts', MAX_ATTEMPTS)
|
||||
self.is_scored = instance_state.get('graded', IS_SCORED) in TRUE_DICT
|
||||
self.accept_file_upload = instance_state.get('accept_file_upload', ACCEPT_FILE_UPLOAD) 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:
|
||||
self.timeinfo = TimeInfo(due_date, grace_period_string)
|
||||
except Exception:
|
||||
@@ -153,7 +123,7 @@ class CombinedOpenEndedV1Module():
|
||||
# Static data is passed to the child modules to render
|
||||
self.static_data = {
|
||||
'max_score': self._max_score,
|
||||
'max_attempts': self.attempts,
|
||||
'max_attempts': self.max_attempts,
|
||||
'prompt': definition['prompt'],
|
||||
'rubric': definition['rubric'],
|
||||
'display_name': self.display_name,
|
||||
@@ -643,15 +613,18 @@ class CombinedOpenEndedV1Module():
|
||||
if not self.ready_to_reset:
|
||||
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 {
|
||||
'success': False,
|
||||
# This is a student_facing_error
|
||||
'error': (
|
||||
'You have attempted this question {0} 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.ready_to_reset = False
|
||||
for i in xrange(0, len(self.task_xml)):
|
||||
@@ -726,7 +699,12 @@ class CombinedOpenEndedV1Module():
|
||||
"""
|
||||
max_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.
|
||||
score_mat = []
|
||||
for i in xrange(0, len(self.task_states)):
|
||||
@@ -739,7 +717,7 @@ class CombinedOpenEndedV1Module():
|
||||
for z in xrange(0, len(score)):
|
||||
if score[z] is None:
|
||||
score[z] = 0
|
||||
score[z] *= float(self.weight)
|
||||
score[z] *= float(weight)
|
||||
score_mat.append(score)
|
||||
|
||||
if len(score_mat) > 0:
|
||||
@@ -753,7 +731,7 @@ class CombinedOpenEndedV1Module():
|
||||
|
||||
if max_score is not None:
|
||||
# Weight the max score if it is not None
|
||||
max_score *= float(self.weight)
|
||||
max_score *= float(weight)
|
||||
else:
|
||||
# Without a max_score, we cannot have a score!
|
||||
score = None
|
||||
|
||||
@@ -9,6 +9,7 @@ from .capa_module import ComplexEncoder
|
||||
from .x_module import XModule
|
||||
from xmodule.raw_module import RawDescriptor
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.modulestore.exceptions import ItemNotFoundError
|
||||
from .timeinfo import TimeInfo
|
||||
from xblock.core import Dict, String, Scope, Boolean, Integer, Float
|
||||
from xmodule.fields import Date
|
||||
@@ -19,36 +20,37 @@ from django.utils.timezone import UTC
|
||||
|
||||
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."
|
||||
|
||||
|
||||
class PeerGradingFields(object):
|
||||
use_for_single_location = Boolean(
|
||||
display_name="Show Single Problem",
|
||||
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.',
|
||||
default=USE_FOR_SINGLE_LOCATION, scope=Scope.settings
|
||||
default=False,
|
||||
scope=Scope.settings
|
||||
)
|
||||
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.',
|
||||
default=LINK_TO_LOCATION, scope=Scope.settings
|
||||
default="",
|
||||
scope=Scope.settings
|
||||
)
|
||||
is_graded = Boolean(
|
||||
graded = Boolean(
|
||||
display_name="Graded",
|
||||
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)
|
||||
grace_period_string = String(help="Amount of grace to give on the due date.", default=None, scope=Scope.settings)
|
||||
max_grade = Integer(
|
||||
help="The maximum grade that a student can receive for this problem.", default=MAX_SCORE,
|
||||
scope=Scope.settings, values={"min": 0}
|
||||
due = 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
|
||||
)
|
||||
student_data_for_location = Dict(
|
||||
help="Student data for a given peer grading problem.",
|
||||
@@ -57,7 +59,8 @@ class PeerGradingFields(object):
|
||||
weight = Float(
|
||||
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.",
|
||||
scope=Scope.settings, values={"min": 0, "step": ".1"}
|
||||
scope=Scope.settings, values={"min": 0, "step": ".1"},
|
||||
default=1
|
||||
)
|
||||
display_name = String(
|
||||
display_name="Display Name",
|
||||
@@ -98,35 +101,31 @@ class PeerGradingModule(PeerGradingFields, XModule):
|
||||
if self.use_for_single_location:
|
||||
try:
|
||||
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(
|
||||
self.link_to_location, self.location))
|
||||
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:
|
||||
self._model_data['due'] = due_date
|
||||
|
||||
try:
|
||||
self.timeinfo = TimeInfo(self.due_date, self.grace_period_string)
|
||||
except:
|
||||
log.error("Error parsing due date information in location {0}".format(location))
|
||||
self.timeinfo = TimeInfo(self.due, self.grace_period_string)
|
||||
except Exception:
|
||||
log.error("Error parsing due date information in location {0}".format(self.location))
|
||||
raise
|
||||
|
||||
self.display_due_date = self.timeinfo.display_due_date
|
||||
|
||||
try:
|
||||
self.student_data_for_location = json.loads(self.student_data_for_location)
|
||||
except:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
self.ajax_url = self.system.ajax_url
|
||||
if not self.ajax_url.endswith("/"):
|
||||
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):
|
||||
return self._closed(self.timeinfo)
|
||||
|
||||
@@ -210,11 +209,16 @@ class PeerGradingModule(PeerGradingFields, XModule):
|
||||
def get_score(self):
|
||||
max_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': 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
|
||||
|
||||
try:
|
||||
@@ -234,11 +238,10 @@ class PeerGradingModule(PeerGradingFields, XModule):
|
||||
# Ensures that once a student receives a final score for peer grading, that it does not change.
|
||||
self.student_data_for_location = response
|
||||
|
||||
if self.weight is not None:
|
||||
score = int(count_graded >= count_required and count_graded > 0) * float(self.weight)
|
||||
total = self.max_grade * float(self.weight)
|
||||
score_dict['score'] = score
|
||||
score_dict['total'] = total
|
||||
score = int(count_graded >= count_required and count_graded > 0) * float(weight)
|
||||
total = float(weight)
|
||||
score_dict['score'] = score
|
||||
score_dict['total'] = total
|
||||
|
||||
return score_dict
|
||||
|
||||
@@ -249,8 +252,8 @@ class PeerGradingModule(PeerGradingFields, XModule):
|
||||
randomization, and 5/7 on another
|
||||
'''
|
||||
max_grade = None
|
||||
if self.use_for_single_location and self.is_graded:
|
||||
max_grade = self.max_grade
|
||||
if self.use_for_single_location and self.graded:
|
||||
max_grade = self.weight
|
||||
return max_grade
|
||||
|
||||
def get_next_submission(self, data):
|
||||
@@ -530,7 +533,7 @@ class PeerGradingModule(PeerGradingFields, XModule):
|
||||
problem_location = problem['location']
|
||||
descriptor = _find_corresponding_module_for_location(problem_location)
|
||||
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)
|
||||
try:
|
||||
problem_timeinfo = TimeInfo(problem['due'], grace_period_string)
|
||||
@@ -617,9 +620,14 @@ class PeerGradingDescriptor(PeerGradingFields, RawDescriptor):
|
||||
#Specify whether or not to pass in open ended interface
|
||||
needs_open_ended_interface = True
|
||||
|
||||
metadata_translations = {
|
||||
'is_graded': 'graded',
|
||||
'attempts': 'max_attempts',
|
||||
'due_data' : 'due'
|
||||
}
|
||||
|
||||
@property
|
||||
def non_editable_metadata_fields(self):
|
||||
non_editable_fields = super(PeerGradingDescriptor, self).non_editable_metadata_fields
|
||||
non_editable_fields.extend([PeerGradingFields.due_date, PeerGradingFields.grace_period_string,
|
||||
PeerGradingFields.max_grade])
|
||||
non_editable_fields.extend([PeerGradingFields.due, PeerGradingFields.grace_period_string])
|
||||
return non_editable_fields
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,24 @@
|
||||
<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>
|
||||
@@ -1,6 +1,7 @@
|
||||
<course>
|
||||
<chapter url_name="Overview">
|
||||
<combinedopenended url_name="SampleQuestion"/>
|
||||
<combinedopenended url_name="SampleQuestion1Attempt"/>
|
||||
<peergrading url_name="PeerGradingSample"/>
|
||||
<peergrading url_name="PeerGradingScored"/>
|
||||
</chapter>
|
||||
|
||||
@@ -15,6 +15,7 @@ from xmodule.course_module import CourseDescriptor
|
||||
from student.models import unique_id_for_user
|
||||
from xmodule.x_module import ModuleSystem
|
||||
from mitxmako.shortcuts import render_to_string
|
||||
from utils import does_location_exist
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -240,7 +241,6 @@ def get_next(request, course_id):
|
||||
return HttpResponse(_get_next(course_id, grader_id, location),
|
||||
mimetype="application/json")
|
||||
|
||||
|
||||
def get_problem_list(request, course_id):
|
||||
"""
|
||||
Get all the problems for the given course id
|
||||
@@ -266,6 +266,20 @@ def get_problem_list(request, course_id):
|
||||
_check_access(request.user, course_id)
|
||||
try:
|
||||
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,
|
||||
mimetype="application/json")
|
||||
except GradingServiceError:
|
||||
|
||||
16
lms/djangoapps/open_ended_grading/utils.py
Normal file
16
lms/djangoapps/open_ended_grading/utils.py
Normal file
@@ -0,0 +1,16 @@
|
||||
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):
|
||||
error_text = ""
|
||||
problem_list = []
|
||||
base_course_url = reverse('courses')
|
||||
list_to_remove = []
|
||||
|
||||
try:
|
||||
#Get list of all open ended problems that the grading server knows about
|
||||
@@ -191,7 +192,6 @@ def student_problem_list(request, course_id):
|
||||
problem_list = problem_list_dict['problem_list']
|
||||
|
||||
#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)):
|
||||
try:
|
||||
#Try to load each problem in the courseware to get links to them
|
||||
|
||||
@@ -10,10 +10,6 @@
|
||||
<div class="grader-status">
|
||||
% if state == 'initial':
|
||||
<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':
|
||||
<span class="grading" id="status_${id}">Submitted for grading.
|
||||
% if eta_message is not None:
|
||||
|
||||
Reference in New Issue
Block a user