Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,9 @@ def get_next_submission(self, problem_location, grader_id):
'success': True,
'submission_id': 1,
'submission_key': "",
'student_response': 'fake student response',
'prompt': 'fake submission prompt',
'rubric': 'fake rubric',
'student_response': 'Sample student response.',
'prompt': 'Sample submission prompt.',
'rubric': 'Placeholder text for the full rubric.',
'max_score': 4
}

Expand All @@ -110,9 +110,9 @@ def show_calibration_essay(self, problem_location, grader_id):
return {'success': True,
'submission_id': 1,
'submission_key': '',
'student_response': 'fake student response',
'prompt': 'fake submission prompt',
'rubric': 'fake rubric',
'student_response': 'Sample student response.',
'prompt': 'Sample submission prompt.',
'rubric': 'Placeholder text for the full rubric.',
'max_score': 4}

def save_calibration_essay(self, **kwargs):
Expand Down
40 changes: 30 additions & 10 deletions common/lib/xmodule/xmodule/peer_grading_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from .capa_module import ComplexEncoder
from .x_module import XModule
from xmodule.raw_module import RawDescriptor
from xmodule.modulestore.exceptions import ItemNotFoundError
from xmodule.modulestore.exceptions import ItemNotFoundError, NoPathToItem
from .timeinfo import TimeInfo
from xblock.fields import Dict, String, Scope, Boolean, Float
from xmodule.fields import Date, Timedelta
Expand Down Expand Up @@ -72,6 +72,12 @@ class PeerGradingFields(object):
scope=Scope.content
)

class InvalidLinkLocation(Exception):
"""
Exception for the case in which a peer grading module tries to link to an invalid location.
"""
pass


class PeerGradingModule(PeerGradingFields, XModule):
"""
Expand Down Expand Up @@ -103,11 +109,21 @@ def __init__(self, *args, **kwargs):

if self.use_for_single_location:
try:
self.linked_problem = self.system.get_module(self.link_to_location)
linked_descriptors = self.descriptor.get_required_module_descriptors()
if len(linked_descriptors) == 0:
error_msg = "Peer grading module {0} is trying to use single problem mode without "
"a location specified.".format(self.location)
log.error(error_msg)
raise InvalidLinkLocation(error_msg)
self.linked_problem = self.system.get_module(linked_descriptors[0])
except ItemNotFoundError:
log.error("Linked location {0} for peer grading module {1} does not exist".format(
self.link_to_location, self.location))
raise
except NoPathToItem:
log.error("Linked location {0} for peer grading module {1} cannot be linked to.".format(
self.link_to_location, self.location))
raise
due_date = self.linked_problem.due
if due_date:
self.due = due_date
Expand Down Expand Up @@ -514,22 +530,26 @@ def peer_grading(self, _data=None):


def _find_corresponding_module_for_location(location):
'''
find the peer grading module that links to the given location
'''
"""
Find the peer grading module that exists at the given location.
"""
try:
return modulestore().get_instance(self.system.course_id, location)
except Exception:
# the linked problem doesn't exist
log.error("Problem {0} does not exist in this course".format(location))
return self.descriptor.system.load_item(location)
except ItemNotFoundError:
# The linked problem doesn't exist.
log.error("Problem {0} does not exist in this course.".format(location))
raise
except NoPathToItem:
# The linked problem does not have a path to it (ie is in a draft or other strange state).
log.error("Cannot find a path to problem {0} in this course.".format(location))
raise

good_problem_list = []
for problem in problem_list:
problem_location = problem['location']
try:
descriptor = _find_corresponding_module_for_location(problem_location)
except Exception:
except (NoPathToItem, ItemNotFoundError):
continue
if descriptor:
problem['due'] = descriptor.due
Expand Down
30 changes: 30 additions & 0 deletions common/lib/xmodule/xmodule/tests/test_peer_grading.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@
from xmodule.modulestore import Location
from .import get_test_system
from test_util_open_ended import MockQueryDict, DummyModulestore
from xmodule.open_ended_grading_classes.peer_grading_service import MockPeerGradingService
import json
from mock import Mock
from xmodule.peer_grading_module import PeerGradingModule
from xblock.field_data import DictFieldData
from xblock.fields import ScopeIds

import logging

Expand Down Expand Up @@ -136,6 +142,13 @@ def test_get_instance_state(self):
"""
self.peer_grading.get_instance_state()

class MockPeerGradingServiceProblemList(MockPeerGradingService):
def get_problem_list(self, course_id, grader_id):
return {'success': True,
'problem_list': [
{"num_graded": 3, "num_pending": 681, "num_required": 3, "location": "i4x://edX/open_ended/combinedopenended/SampleQuestion", "problem_name": "Peer-Graded Essay"},
]}

class PeerGradingModuleScoredTest(unittest.TestCase, DummyModulestore):
"""
Test peer grading xmodule at the unit level. More detailed tests are difficult, as the module relies on an
Expand All @@ -155,3 +168,20 @@ def setUp(self):
def test_metadata_load(self):
peer_grading = self.get_module_from_location(self.problem_location, COURSE)
self.assertEqual(peer_grading.closed(), False)

def test_problem_list(self):
"""
Test to see if a peer grading problem list can be correctly initialized.
"""

# Initialize peer grading module.
peer_grading = self.get_module_from_location(self.problem_location, COURSE)

# Ensure that it cannot find any peer grading.
html = peer_grading.peer_grading()
self.assertNotIn("Peer-Graded", html)

# Swap for our mock class, which will find peer grading.
peer_grading.peer_gs = MockPeerGradingServiceProblemList()
html = peer_grading.peer_grading()
self.assertIn("Peer-Graded", html)
1 change: 1 addition & 0 deletions common/test/data/open_ended/course/2012_Fall.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
<combinedopenended url_name="SampleQuestion1Attempt"/>
<peergrading url_name="PeerGradingSample"/>
<peergrading url_name="PeerGradingScored"/>
<peergrading url_name="PeerGradingLinked"/>
</chapter>
</course>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<peergrading is_graded="True" max_grade="1" use_for_single_location="True" link_to_location="i4x://edX/open_ended/combinedopenended/SampleQuestion"/>
1 change: 1 addition & 0 deletions common/test/data/open_ended_nopath/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This is a very very simple course, useful for debugging open ended grading code. This is specifically for testing if a peer grading module with no path to it in the course will be handled properly.
1 change: 1 addition & 0 deletions common/test/data/open_ended_nopath/course.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<course org="edX" course="open_ended_nopath" url_name="2012_Fall"/>
4 changes: 4 additions & 0 deletions common/test/data/open_ended_nopath/course/2012_Fall.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<course>
<chapter url_name="Overview">
</chapter>
</course>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<peergrading/>
11 changes: 11 additions & 0 deletions common/test/data/open_ended_nopath/policies/2012_Fall.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"course/2012_Fall": {
"graceperiod": "2 days 5 hours 59 minutes 59 seconds",
"start": "2015-07-17T12:00",
"display_name": "Self Assessment Test",
"graded": "true"
},
"chapter/Overview": {
"display_name": "Overview"
}
}
1 change: 1 addition & 0 deletions lms/djangoapps/courseware/tests/modulestore_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,6 @@
'edX/test_about_blob_end_date/2012_Fall': 'xml',
'edX/graded/2012_Fall': 'xml',
'edX/open_ended/2012_Fall': 'xml',
'edX/open_ended_nopath/2012_Fall': 'xml',
}
TEST_DATA_MIXED_MODULESTORE = mixed_store_config(TEST_DATA_DIR, MAPPINGS)
19 changes: 19 additions & 0 deletions lms/djangoapps/open_ended_grading/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -320,3 +320,22 @@ def test_problem_list(self):
request = Mock(user=self.user)
response = views.student_problem_list(request, self.course.id)
self.assertRegexpMatches(response.content, "Here are a list of open ended problems for this course.")

@override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE)
class TestPeerGradingFound(ModuleStoreTestCase):
"""
Test to see if peer grading modules can be found properly.
"""

def setUp(self):
self.course_name = 'edX/open_ended_nopath/2012_Fall'
self.course = modulestore().get_course(self.course_name)

def test_peer_grading_nopath(self):
"""
The open_ended_nopath course contains a peer grading module with no path to it.
Ensure that the exception is caught.
"""

found, url = views.find_peer_grading_module(self.course)
self.assertEqual(found, False)
25 changes: 16 additions & 9 deletions lms/djangoapps/open_ended_grading/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

from xmodule.modulestore.django import modulestore
from xmodule.modulestore import search
from xmodule.modulestore.exceptions import ItemNotFoundError
from xmodule.modulestore.exceptions import ItemNotFoundError, NoPathToItem

from django.http import HttpResponse, Http404, HttpResponseRedirect
from mitxmako.shortcuts import render_to_string
Expand Down Expand Up @@ -97,25 +97,32 @@ def find_peer_grading_module(course):
@param course: A course object.
@return: boolean found_module, string problem_url
"""
#Reverse the base course url

# Reverse the base course url.
base_course_url = reverse('courses')
found_module = False
problem_url = ""

#Get the course id and split it
# Get the course id and split it.
course_id_parts = course.id.split("/")
log.info("COURSE ID PARTS")
log.info(course_id_parts)
#Get the peer grading modules currently in the course. Explicitly specify the course id to avoid issues with different runs.
# Get the peer grading modules currently in the course. Explicitly specify the course id to avoid issues with different runs.
items = modulestore().get_items(['i4x', course_id_parts[0], course_id_parts[1], 'peergrading', None],
course_id=course.id)
#See if any of the modules are centralized modules (ie display info from multiple problems)
items = [i for i in items if not getattr(i, "use_for_single_location", True)]
#Get the first one
if len(items) > 0:
item_location = items[0].location
#Generate a url for the first module and redirect the user to it
problem_url_parts = search.path_to_location(modulestore(), course.id, item_location)
# Loop through all potential peer grading modules, and find the first one that has a path to it.
for item in items:
item_location = item.location
# Generate a url for the first module and redirect the user to it.
try:
problem_url_parts = search.path_to_location(modulestore(), course.id, item_location)
except NoPathToItem:
# In the case of nopathtoitem, the peer grading module that was found is in an invalid state, and
# can no longer be accessed. Log an informational message, but this will not impact normal behavior.
log.info("Invalid peer grading module location {0} in course {1}. This module may need to be removed.".format(item_location, course.id))
continue
problem_url = generate_problem_url(problem_url_parts, base_course_url)
found_module = True

Expand Down