diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py
index 2da15a4086..eaea908f3a 100644
--- a/common/lib/xmodule/xmodule/combined_open_ended_module.py
+++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py
@@ -1,25 +1,13 @@
-import copy
-from fs.errors import ResourceNotFoundError
-import itertools
import json
import logging
from lxml import etree
-from lxml.html import rewrite_links
-from path import path
-import os
-import sys
from pkg_resources import resource_string
-from .capa_module import only_one, ComplexEncoder
from .editing_module import EditingDescriptor
-from .html_checker import check_html
-from progress import Progress
-from .stringify import stringify_children
from .x_module import XModule
from .xml_module import XmlDescriptor
-from xmodule.modulestore import Location
-from combined_open_ended_modulev1 import CombinedOpenEndedV1Module, CombinedOpenEndedV1Descriptor
+from xmodule.open_ended_grading_classes.combined_open_ended_modulev1 import CombinedOpenEndedV1Module, CombinedOpenEndedV1Descriptor
log = logging.getLogger("mitx.courseware")
@@ -217,4 +205,4 @@ class CombinedOpenEndedDescriptor(XmlDescriptor, EditingDescriptor):
for child in ['task']:
add_child(child)
- return elt
\ No newline at end of file
+ return elt
diff --git a/common/lib/xmodule/xmodule/open_ended_grading_classes/__init__.py b/common/lib/xmodule/xmodule/open_ended_grading_classes/__init__.py
new file mode 100644
index 0000000000..9aa77fde52
--- /dev/null
+++ b/common/lib/xmodule/xmodule/open_ended_grading_classes/__init__.py
@@ -0,0 +1 @@
+__author__ = 'vik'
diff --git a/common/lib/xmodule/xmodule/combined_open_ended_modulev1.py b/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py
similarity index 93%
rename from common/lib/xmodule/xmodule/combined_open_ended_modulev1.py
rename to common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py
index ce5d55d7b7..0e3bd86581 100644
--- a/common/lib/xmodule/xmodule/combined_open_ended_modulev1.py
+++ b/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py
@@ -1,33 +1,18 @@
-import copy
-from fs.errors import ResourceNotFoundError
-import itertools
import json
import logging
from lxml import etree
from lxml.html import rewrite_links
-from path import path
-import os
-import sys
-import re
-
-from pkg_resources import resource_string
-
-from .capa_module import only_one, ComplexEncoder
-from .editing_module import EditingDescriptor
-from .html_checker import check_html
-from progress import Progress
-from .stringify import stringify_children
-from .x_module import XModule
-from .xml_module import XmlDescriptor
-from xmodule.modulestore import Location
+from xmodule.timeinfo import TimeInfo
+from xmodule.capa_module import only_one, ComplexEncoder
+from xmodule.editing_module import EditingDescriptor
+from xmodule.html_checker import check_html
+from xmodule.progress import Progress
+from xmodule.stringify import stringify_children
+from xmodule.x_module import XModule
+from xmodule.xml_module import XmlDescriptor
import self_assessment_module
import open_ended_module
-from combined_open_ended_rubric import CombinedOpenEndedRubric, RubricParsingError, GRADER_TYPE_IMAGE_DICT, HUMAN_GRADER_TYPE, LEGEND_LIST
-from .stringify import stringify_children
-import dateutil
-import dateutil.parser
-import datetime
-from timeparse import parse_timedelta
+from combined_open_ended_rubric import CombinedOpenEndedRubric, GRADER_TYPE_IMAGE_DICT, HUMAN_GRADER_TYPE, LEGEND_LIST
log = logging.getLogger("mitx.courseware")
@@ -59,6 +44,10 @@ HUMAN_TASK_TYPE = {
'openended' : "edX Assessment",
}
+#Default value that controls whether or not to skip basic spelling checks in the controller
+#Metadata overrides this
+SKIP_BASIC_CHECKS = False
+
class CombinedOpenEndedV1Module():
"""
This is a module that encapsulates all open ended grading (self assessment, peer assessment, etc).
@@ -73,7 +62,7 @@ class CombinedOpenEndedV1Module():
'save_assessment' -- Saves the student assessment (or external grader assessment)
'save_post_assessment' -- saves a post assessment (hint, feedback on feedback, etc)
ajax actions implemented by combined open ended module are:
- 'reset' -- resets the whole combined open ended module and returns to the first child module
+ 'reset' -- resets the whole combined open ended module and returns to the first child moduleresource_string
'next_problem' -- moves to the next child module
'get_results' -- gets results from a given child module
@@ -90,14 +79,6 @@ class CombinedOpenEndedV1Module():
INTERMEDIATE_DONE = 'intermediate_done'
DONE = 'done'
- js = {'coffee': [resource_string(__name__, 'js/src/combinedopenended/display.coffee'),
- resource_string(__name__, 'js/src/collapsible.coffee'),
- resource_string(__name__, 'js/src/javascript_loader.coffee'),
- ]}
- js_module_name = "CombinedOpenEnded"
-
- css = {'scss': [resource_string(__name__, 'css/combinedopenended/display.scss')]}
-
def __init__(self, system, location, definition, descriptor,
instance_state=None, shared_state=None, metadata = None, static_data = None, **kwargs):
@@ -165,28 +146,16 @@ class CombinedOpenEndedV1Module():
self.max_attempts = int(self.metadata.get('attempts', MAX_ATTEMPTS))
self.is_scored = self.metadata.get('is_graded', IS_SCORED) in TRUE_DICT
self.accept_file_upload = self.metadata.get('accept_file_upload', ACCEPT_FILE_UPLOAD) in TRUE_DICT
+ self.skip_basic_checks = self.metadata.get('skip_spelling_checks', SKIP_BASIC_CHECKS)
display_due_date_string = self.metadata.get('due', None)
- if display_due_date_string is not None:
- try:
- self.display_due_date = dateutil.parser.parse(display_due_date_string)
- except ValueError:
- log.error("Could not parse due date {0} for location {1}".format(display_due_date_string, location))
- raise
- else:
- self.display_due_date = None
-
grace_period_string = self.metadata.get('graceperiod', None)
- if grace_period_string is not None and self.display_due_date:
- try:
- self.grace_period = parse_timedelta(grace_period_string)
- self.close_date = self.display_due_date + self.grace_period
- except:
- log.error("Error parsing the grace period {0} for location {1}".format(grace_period_string, location))
- raise
- else:
- self.grace_period = None
- self.close_date = self.display_due_date
+ try:
+ self.timeinfo = TimeInfo(display_due_date_string, grace_period_string)
+ except:
+ log.error("Error parsing due date information in location {0}".format(location))
+ raise
+ self.display_due_date = self.timeinfo.display_due_date
# Used for progress / grading. Currently get credit just for
# completion (doesn't matter if you self-assessed correct/incorrect).
@@ -204,7 +173,9 @@ class CombinedOpenEndedV1Module():
'rubric': definition['rubric'],
'display_name': self.display_name,
'accept_file_upload': self.accept_file_upload,
- 'close_date' : self.close_date,
+ 'close_date' : self.timeinfo.close_date,
+ 's3_interface' : self.system.s3_interface,
+ 'skip_basic_checks' : self.skip_basic_checks,
}
self.task_xml = definition['task_xml']
@@ -798,9 +769,6 @@ class CombinedOpenEndedV1Descriptor(XmlDescriptor, EditingDescriptor):
has_score = True
template_dir_name = "combinedopenended"
- js = {'coffee': [resource_string(__name__, 'js/src/html/edit.coffee')]}
- js_module_name = "HTMLEditingDescriptor"
-
@classmethod
def definition_from_xml(cls, xml_object, system):
"""
@@ -841,4 +809,4 @@ class CombinedOpenEndedV1Descriptor(XmlDescriptor, EditingDescriptor):
for child in ['task']:
add_child(child)
- return elt
\ No newline at end of file
+ return elt
diff --git a/common/lib/xmodule/xmodule/combined_open_ended_rubric.py b/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_rubric.py
similarity index 100%
rename from common/lib/xmodule/xmodule/combined_open_ended_rubric.py
rename to common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_rubric.py
diff --git a/common/lib/xmodule/xmodule/grading_service_module.py b/common/lib/xmodule/xmodule/open_ended_grading_classes/grading_service_module.py
similarity index 96%
rename from common/lib/xmodule/xmodule/grading_service_module.py
rename to common/lib/xmodule/xmodule/open_ended_grading_classes/grading_service_module.py
index 9af28a72c5..6bd7a6fd0e 100644
--- a/common/lib/xmodule/xmodule/grading_service_module.py
+++ b/common/lib/xmodule/xmodule/open_ended_grading_classes/grading_service_module.py
@@ -5,7 +5,7 @@ import requests
from requests.exceptions import RequestException, ConnectionError, HTTPError
import sys
-from xmodule.combined_open_ended_rubric import CombinedOpenEndedRubric, RubricParsingError
+from combined_open_ended_rubric import CombinedOpenEndedRubric
from lxml import etree
log = logging.getLogger(__name__)
@@ -22,8 +22,6 @@ class GradingService(object):
def __init__(self, config):
self.username = config['username']
self.password = config['password']
- self.url = config['url']
- self.login_url = self.url + '/login/'
self.session = requests.session()
self.system = config['system']
diff --git a/common/lib/xmodule/xmodule/open_ended_image_submission.py b/common/lib/xmodule/xmodule/open_ended_grading_classes/open_ended_image_submission.py
similarity index 93%
rename from common/lib/xmodule/xmodule/open_ended_image_submission.py
rename to common/lib/xmodule/xmodule/open_ended_grading_classes/open_ended_image_submission.py
index 66500146ed..88921c1429 100644
--- a/common/lib/xmodule/xmodule/open_ended_image_submission.py
+++ b/common/lib/xmodule/xmodule/open_ended_grading_classes/open_ended_image_submission.py
@@ -13,11 +13,6 @@ from urlparse import urlparse
import requests
from boto.s3.connection import S3Connection
from boto.s3.key import Key
-#TODO: Settings import is needed now in order to specify the URL and keys for amazon s3 (to upload images).
-#Eventually, the goal is to replace the global django settings import with settings specifically
-#for this module. There is no easy way to do this now, so piggybacking on the django settings
-#makes sense.
-from django.conf import settings
import pickle
import logging
import re
@@ -221,7 +216,7 @@ def run_image_tests(image):
return success
-def upload_to_s3(file_to_upload, keyname):
+def upload_to_s3(file_to_upload, keyname, s3_interface):
'''
Upload file to S3 using provided keyname.
@@ -237,8 +232,8 @@ def upload_to_s3(file_to_upload, keyname):
#im.save(out_im, 'PNG')
try:
- conn = S3Connection(settings.AWS_ACCESS_KEY_ID, settings.AWS_SECRET_ACCESS_KEY)
- bucketname = str(settings.AWS_STORAGE_BUCKET_NAME)
+ conn = S3Connection(s3_interface['access_key'], s3_interface['secret_access_key'])
+ bucketname = str(s3_interface['storage_bucket_name'])
bucket = conn.create_bucket(bucketname.lower())
k = Key(bucket)
diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_grading_classes/open_ended_module.py
similarity index 97%
rename from common/lib/xmodule/xmodule/open_ended_module.py
rename to common/lib/xmodule/xmodule/open_ended_grading_classes/open_ended_module.py
index 98260f3401..95c631c8fd 100644
--- a/common/lib/xmodule/xmodule/open_ended_module.py
+++ b/common/lib/xmodule/xmodule/open_ended_grading_classes/open_ended_module.py
@@ -5,28 +5,16 @@ hints, answers, and assessment judgment (currently only correct/incorrect).
Parses xml definition file--see below for exact format.
"""
-import copy
-from fs.errors import ResourceNotFoundError
-import itertools
import json
import logging
from lxml import etree
-from lxml.html import rewrite_links
-from path import path
-import os
-import sys
-import hashlib
import capa.xqueue_interface as xqueue_interface
-from pkg_resources import resource_string
-
-from .capa_module import only_one, ComplexEncoder
-from .editing_module import EditingDescriptor
-from .html_checker import check_html
-from progress import Progress
-from .stringify import stringify_children
-from .xml_module import XmlDescriptor
-from xmodule.modulestore import Location
+from xmodule.capa_module import ComplexEncoder
+from xmodule.editing_module import EditingDescriptor
+from xmodule.progress import Progress
+from xmodule.stringify import stringify_children
+from xmodule.xml_module import XmlDescriptor
from capa.util import *
import openendedchild
@@ -122,7 +110,8 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
'rubric': rubric_string,
'initial_display': self.initial_display,
'answer': self.answer,
- 'problem_id': self.display_name
+ 'problem_id': self.display_name,
+ 'skip_basic_checks': self.skip_basic_checks,
})
updated_grader_payload = json.dumps(parsed_grader_payload)
@@ -689,9 +678,6 @@ class OpenEndedDescriptor(XmlDescriptor, EditingDescriptor):
has_score = True
template_dir_name = "openended"
- js = {'coffee': [resource_string(__name__, 'js/src/html/edit.coffee')]}
- js_module_name = "HTMLEditingDescriptor"
-
@classmethod
def definition_from_xml(cls, xml_object, system):
"""
diff --git a/common/lib/xmodule/xmodule/openendedchild.py b/common/lib/xmodule/xmodule/open_ended_grading_classes/openendedchild.py
similarity index 96%
rename from common/lib/xmodule/xmodule/openendedchild.py
rename to common/lib/xmodule/xmodule/open_ended_grading_classes/openendedchild.py
index c83b0f0ea3..1700dcaa07 100644
--- a/common/lib/xmodule/xmodule/openendedchild.py
+++ b/common/lib/xmodule/xmodule/open_ended_grading_classes/openendedchild.py
@@ -13,17 +13,15 @@ import hashlib
import capa.xqueue_interface as xqueue_interface
import re
-from pkg_resources import resource_string
-
-from .capa_module import only_one, ComplexEncoder
-from .editing_module import EditingDescriptor
-from .html_checker import check_html
-from progress import Progress
-from .stringify import stringify_children
-from .xml_module import XmlDescriptor
+from xmodule.capa_module import only_one, ComplexEncoder
+import open_ended_image_submission
+from xmodule.editing_module import EditingDescriptor
+from xmodule.html_checker import check_html
+from xmodule.progress import Progress
+from xmodule.stringify import stringify_children
+from xmodule.xml_module import XmlDescriptor
from xmodule.modulestore import Location
from capa.util import *
-import open_ended_image_submission
from datetime import datetime
@@ -100,6 +98,8 @@ class OpenEndedChild(object):
self.display_name = static_data['display_name']
self.accept_file_upload = static_data['accept_file_upload']
self.close_date = static_data['close_date']
+ self.s3_interface = static_data['s3_interface']
+ self.skip_basic_checks = static_data['skip_basic_checks']
# Used for progress / grading. Currently get credit just for
# completion (doesn't matter if you self-assessed correct/incorrect).
@@ -319,7 +319,7 @@ class OpenEndedChild(object):
try:
image_data.seek(0)
- success, s3_public_url = open_ended_image_submission.upload_to_s3(image_data, image_key)
+ success, s3_public_url = open_ended_image_submission.upload_to_s3(image_data, image_key, self.s3_interface)
except:
log.exception("Could not upload image to S3.")
diff --git a/common/lib/xmodule/xmodule/peer_grading_service.py b/common/lib/xmodule/xmodule/open_ended_grading_classes/peer_grading_service.py
similarity index 83%
rename from common/lib/xmodule/xmodule/peer_grading_service.py
rename to common/lib/xmodule/xmodule/open_ended_grading_classes/peer_grading_service.py
index 9363097a1f..be1ff5bef6 100644
--- a/common/lib/xmodule/xmodule/peer_grading_service.py
+++ b/common/lib/xmodule/xmodule/open_ended_grading_classes/peer_grading_service.py
@@ -1,18 +1,7 @@
import json
import logging
-import requests
-from requests.exceptions import RequestException, ConnectionError, HTTPError
-import sys
-#TODO: Settings import is needed now in order to specify the URL where to find the peer grading service.
-#Eventually, the goal is to replace the global django settings import with settings specifically
-#for this xmodule. There is no easy way to do this now, so piggybacking on the django settings
-#makes sense.
-from django.conf import settings
-
-from combined_open_ended_rubric import CombinedOpenEndedRubric, RubricParsingError
-from lxml import etree
-from grading_service_module import GradingService, GradingServiceError
+from grading_service_module import GradingService
log = logging.getLogger(__name__)
@@ -28,6 +17,8 @@ class PeerGradingService(GradingService):
def __init__(self, config, system):
config['system'] = system
super(PeerGradingService, self).__init__(config)
+ self.url = config['url'] + config['peer_grading']
+ self.login_url = self.url + '/login/'
self.get_next_submission_url = self.url + '/get_next_submission/'
self.save_grade_url = self.url + '/save_grade/'
self.is_student_calibrated_url = self.url + '/is_student_calibrated/'
@@ -143,25 +134,3 @@ class MockPeerGradingService(object):
json.dumps({'location': 'i4x://MITx/3.091x/problem/open_ended_demo2',
'problem_name': "Problem 2", 'num_graded': 1, 'num_pending': 5})
]})
-
-_service = None
-
-
-def peer_grading_service(system):
- """
- Return a peer grading service instance--if settings.MOCK_PEER_GRADING is True,
- returns a mock one, otherwise a real one.
-
- Caches the result, so changing the setting after the first call to this
- function will have no effect.
- """
- global _service
- if _service is not None:
- return _service
-
- if settings.MOCK_PEER_GRADING:
- _service = MockPeerGradingService()
- else:
- _service = PeerGradingService(settings.PEER_GRADING_INTERFACE, system)
-
- return _service
diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/open_ended_grading_classes/self_assessment_module.py
similarity index 93%
rename from common/lib/xmodule/xmodule/self_assessment_module.py
rename to common/lib/xmodule/xmodule/open_ended_grading_classes/self_assessment_module.py
index 0d1092f96f..c608eeea06 100644
--- a/common/lib/xmodule/xmodule/self_assessment_module.py
+++ b/common/lib/xmodule/xmodule/open_ended_grading_classes/self_assessment_module.py
@@ -1,24 +1,12 @@
-import copy
-from fs.errors import ResourceNotFoundError
-import itertools
import json
import logging
from lxml import etree
-from lxml.html import rewrite_links
-from path import path
-import os
-import sys
-from pkg_resources import resource_string
-
-from .capa_module import only_one, ComplexEncoder
-from .editing_module import EditingDescriptor
-from .html_checker import check_html
-from progress import Progress
-from .stringify import stringify_children
-from .x_module import XModule
-from .xml_module import XmlDescriptor
-from xmodule.modulestore import Location
+from xmodule.capa_module import ComplexEncoder
+from xmodule.editing_module import EditingDescriptor
+from xmodule.progress import Progress
+from xmodule.stringify import stringify_children
+from xmodule.xml_module import XmlDescriptor
import openendedchild
from combined_open_ended_rubric import CombinedOpenEndedRubric
@@ -285,10 +273,6 @@ class SelfAssessmentDescriptor(XmlDescriptor, EditingDescriptor):
has_score = True
template_dir_name = "selfassessment"
- js = {'coffee': [resource_string(__name__, 'js/src/html/edit.coffee')]}
- js_module_name = "HTMLEditingDescriptor"
- css = {'scss': [resource_string(__name__, 'css/editor/edit.scss'), resource_string(__name__, 'css/html/edit.scss')]}
-
@classmethod
def definition_from_xml(cls, xml_object, system):
"""
diff --git a/common/lib/xmodule/xmodule/peer_grading_module.py b/common/lib/xmodule/xmodule/peer_grading_module.py
index 20f71f3b3c..4dfe8e0dfa 100644
--- a/common/lib/xmodule/xmodule/peer_grading_module.py
+++ b/common/lib/xmodule/xmodule/peer_grading_module.py
@@ -1,39 +1,20 @@
-"""
-This module provides an interface on the grading-service backend
-for peer grading
-
-Use peer_grading_service() to get the version specified
-in settings.PEER_GRADING_INTERFACE
-
-"""
import json
import logging
-import requests
-import sys
-from django.conf import settings
-
-from combined_open_ended_rubric import CombinedOpenEndedRubric
from lxml import etree
-import copy
-import itertools
-import json
-import logging
-from lxml.html import rewrite_links
-import os
-
+from datetime import datetime
from pkg_resources import resource_string
-from .capa_module import only_one, ComplexEncoder
+from .capa_module import ComplexEncoder
from .editing_module import EditingDescriptor
-from .html_checker import check_html
-from progress import Progress
from .stringify import stringify_children
from .x_module import XModule
from .xml_module import XmlDescriptor
from xmodule.modulestore import Location
+from xmodule.modulestore.django import modulestore
+from timeinfo import TimeInfo
-from peer_grading_service import peer_grading_service, GradingServiceError
+from xmodule.open_ended_grading_classes.peer_grading_service import PeerGradingService, GradingServiceError
log = logging.getLogger(__name__)
@@ -70,7 +51,8 @@ class PeerGradingModule(XModule):
#We need to set the location here so the child modules can use it
system.set('location', location)
self.system = system
- self.peer_gs = peer_grading_service(self.system)
+ self.peer_gs = PeerGradingService(self.system.open_ended_grading_interface, self.system)
+
self.use_for_single_location = self.metadata.get('use_for_single_location', USE_FOR_SINGLE_LOCATION)
if isinstance(self.use_for_single_location, basestring):
@@ -80,10 +62,28 @@ class PeerGradingModule(XModule):
if isinstance(self.is_graded, basestring):
self.is_graded = (self.is_graded in TRUE_DICT)
+ display_due_date_string = self.metadata.get('due', None)
+ grace_period_string = self.metadata.get('graceperiod', None)
+
+ try:
+ self.timeinfo = TimeInfo(display_due_date_string, grace_period_string)
+ except:
+ log.error("Error parsing due date information in location {0}".format(location))
+ raise
+
+ self.display_due_date = self.timeinfo.display_due_date
+
self.link_to_location = self.metadata.get('link_to_location', USE_FOR_SINGLE_LOCATION)
if self.use_for_single_location == True:
- #This will raise an exception if the location is invalid
- link_to_location_object = Location(self.link_to_location)
+ try:
+ self.linked_problem = modulestore().get_instance(self.system.course_id, self.link_to_location)
+ except:
+ 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.metadata.get('peer_grading_due', None)
+ if due_date:
+ self.metadata['due'] = due_date
self.ajax_url = self.system.ajax_url
if not self.ajax_url.endswith("/"):
@@ -95,6 +95,15 @@ class PeerGradingModule(XModule):
#This could result in an exception, but not wrapping in a try catch block so it moves up the stack
self.max_grade = int(self.max_grade)
+ def closed(self):
+ return self._closed(self.timeinfo)
+
+ def _closed(self, timeinfo):
+ if timeinfo.close_date is not None and datetime.utcnow() > timeinfo.close_date:
+ return True
+ return False
+
+
def _err_response(self, msg):
"""
Return a HttpResponse with a json dump with success=False, and the given error message.
@@ -114,6 +123,8 @@ class PeerGradingModule(XModule):
Needs to be implemented by inheritors. Renders the HTML that students see.
@return:
"""
+ if self.closed():
+ return self.peer_grading_closed()
if not self.use_for_single_location:
return self.peer_grading()
else:
@@ -142,7 +153,7 @@ class PeerGradingModule(XModule):
def query_data_for_location(self):
student_id = self.system.anonymous_student_id
- location = self.system.location
+ location = self.link_to_location
success = False
response = {}
@@ -171,7 +182,7 @@ class PeerGradingModule(XModule):
success, response = self.query_data_for_location()
if not success:
log.exception("No instance data found and could not get data from controller for loc {0} student {1}".format(
- self.system.location, self.system.anonymous_student_id
+ self.system.location.url(), self.system.anonymous_student_id
))
return None
count_graded = response['count_graded']
@@ -400,6 +411,16 @@ class PeerGradingModule(XModule):
log.exception("Error saving calibration grade, location: {0}, submission_id: {1}, submission_key: {2}, grader_id: {3}".format(location, submission_id, submission_key, grader_id))
return self._err_response('Could not connect to grading service')
+ def peer_grading_closed(self):
+ '''
+ Show the Peer grading closed template
+ '''
+ html = self.system.render_template('peer_grading/peer_grading_closed.html', {
+ 'use_for_single_location': self.use_for_single_location
+ })
+ return html
+
+
def peer_grading(self, get=None):
'''
Show a peer grading interface
@@ -426,6 +447,40 @@ class PeerGradingModule(XModule):
error_text = "Could not get problem list"
success = False
+
+ def _find_corresponding_module_for_location(location):
+ '''
+ find the peer grading module that links to the given location
+ '''
+ try:
+ return modulestore().get_instance(self.system.course_id, location)
+ except:
+ # the linked problem doesn't exist
+ log.error("Problem {0} does not exist in this course".format(location))
+ raise
+
+
+ for problem in problem_list:
+ problem_location = problem['location']
+ descriptor = _find_corresponding_module_for_location(problem_location)
+ if descriptor:
+ problem['due'] = descriptor.metadata.get('peer_grading_due', None)
+ grace_period_string = descriptor.metadata.get('graceperiod', None)
+ try:
+ problem_timeinfo = TimeInfo(problem['due'], grace_period_string)
+ except:
+ log.error("Malformed due date or grace period string for location {0}".format(problem_location))
+ raise
+ if self._closed(problem_timeinfo):
+ problem['closed'] = True
+ else:
+ problem['closed'] = False
+ else:
+ # if we can't find the due date, assume that it doesn't have one
+ problem['due'] = None
+ problem['closed'] = False
+
+
ajax_url = self.ajax_url
html = self.system.render_template('peer_grading/peer_grading.html', {
'course_id': self.system.course_id,
diff --git a/common/lib/xmodule/xmodule/tests/test_combined_open_ended.py b/common/lib/xmodule/xmodule/tests/test_combined_open_ended.py
index c2b27e4953..5f6496f823 100644
--- a/common/lib/xmodule/xmodule/tests/test_combined_open_ended.py
+++ b/common/lib/xmodule/xmodule/tests/test_combined_open_ended.py
@@ -2,9 +2,9 @@ import json
from mock import Mock, MagicMock, ANY
import unittest
-from xmodule.openendedchild import OpenEndedChild
-from xmodule.open_ended_module import OpenEndedModule
-from xmodule.combined_open_ended_modulev1 import CombinedOpenEndedV1Module
+from xmodule.open_ended_grading_classes.openendedchild import OpenEndedChild
+from xmodule.open_ended_grading_classes.open_ended_module import OpenEndedModule
+from xmodule.open_ended_grading_classes.combined_open_ended_modulev1 import CombinedOpenEndedV1Module
from xmodule.modulestore import Location
from lxml import etree
@@ -12,6 +12,8 @@ import capa.xqueue_interface as xqueue_interface
from datetime import datetime
from . import test_system
+
+import test_util_open_ended
"""
Tests for the various pieces of the CombinedOpenEndedGrading system
@@ -43,7 +45,10 @@ class OpenEndedChildTest(unittest.TestCase):
'max_score': max_score,
'display_name': 'Name',
'accept_file_upload': False,
- 'close_date': None
+ 'close_date': None,
+ 's3_interface' : "",
+ 'open_ended_grading_interface' : {},
+ 'skip_basic_checks' : False,
}
definition = Mock()
descriptor = Mock()
@@ -161,6 +166,9 @@ class OpenEndedModuleTest(unittest.TestCase):
'accept_file_upload': False,
'rewrite_content_links' : "",
'close_date': None,
+ 's3_interface' : test_util_open_ended.S3_INTERFACE,
+ 'open_ended_grading_interface' : test_util_open_ended.OPEN_ENDED_GRADING_INTERFACE,
+ 'skip_basic_checks' : False,
}
oeparam = etree.XML('''
@@ -293,6 +301,9 @@ class CombinedOpenEndedModuleTest(unittest.TestCase):
'accept_file_upload' : False,
'rewrite_content_links' : "",
'close_date' : "",
+ 's3_interface' : test_util_open_ended.S3_INTERFACE,
+ 'open_ended_grading_interface' : test_util_open_ended.OPEN_ENDED_GRADING_INTERFACE,
+ 'skip_basic_checks' : False,
}
oeparam = etree.XML('''
diff --git a/common/lib/xmodule/xmodule/tests/test_self_assessment.py b/common/lib/xmodule/xmodule/tests/test_self_assessment.py
index ca444f9083..f197aebc08 100644
--- a/common/lib/xmodule/xmodule/tests/test_self_assessment.py
+++ b/common/lib/xmodule/xmodule/tests/test_self_assessment.py
@@ -2,12 +2,13 @@ import json
from mock import Mock, MagicMock
import unittest
-from xmodule.self_assessment_module import SelfAssessmentModule
+from xmodule.open_ended_grading_classes.self_assessment_module import SelfAssessmentModule
from xmodule.modulestore import Location
from lxml import etree
from . import test_system
+import test_util_open_ended
class SelfAssessmentTest(unittest.TestCase):
@@ -46,7 +47,10 @@ class SelfAssessmentTest(unittest.TestCase):
'max_score': 1,
'display_name': "Name",
'accept_file_upload': False,
- 'close_date': None
+ 'close_date': None,
+ 's3_interface' : test_util_open_ended.S3_INTERFACE,
+ 'open_ended_grading_interface' : test_util_open_ended.OPEN_ENDED_GRADING_INTERFACE,
+ 'skip_basic_checks' : False,
}
self.module = SelfAssessmentModule(test_system, self.location,
diff --git a/common/lib/xmodule/xmodule/tests/test_util_open_ended.py b/common/lib/xmodule/xmodule/tests/test_util_open_ended.py
new file mode 100644
index 0000000000..8d1fcd30ce
--- /dev/null
+++ b/common/lib/xmodule/xmodule/tests/test_util_open_ended.py
@@ -0,0 +1,14 @@
+OPEN_ENDED_GRADING_INTERFACE = {
+ 'url' : 'http://127.0.0.1:3033/',
+ 'username' : 'incorrect',
+ 'password' : 'incorrect',
+ 'staff_grading' : 'staff_grading',
+ 'peer_grading' : 'peer_grading',
+ 'grading_controller' : 'grading_controller'
+}
+
+S3_INTERFACE = {
+ 'aws_access_key' : "",
+ 'aws_secret_key' : "",
+ "aws_bucket_name" : "",
+}
\ No newline at end of file
diff --git a/common/lib/xmodule/xmodule/timeinfo.py b/common/lib/xmodule/xmodule/timeinfo.py
new file mode 100644
index 0000000000..6c6a72e700
--- /dev/null
+++ b/common/lib/xmodule/xmodule/timeinfo.py
@@ -0,0 +1,39 @@
+import dateutil
+import dateutil.parser
+import datetime
+from timeparse import parse_timedelta
+
+import logging
+log = logging.getLogger(__name__)
+
+class TimeInfo(object):
+ """
+ This is a simple object that calculates and stores datetime information for an XModule
+ based on the due date string and the grace period string
+
+ So far it parses out three different pieces of time information:
+ self.display_due_date - the 'official' due date that gets displayed to students
+ self.grace_period - the length of the grace period
+ self.close_date - the real due date
+
+ """
+ def __init__(self, display_due_date_string, grace_period_string):
+ if display_due_date_string is not None:
+ try:
+ self.display_due_date = dateutil.parser.parse(display_due_date_string)
+ except ValueError:
+ log.error("Could not parse due date {0}".format(display_due_date_string))
+ raise
+ else:
+ self.display_due_date = None
+
+ if grace_period_string is not None and self.display_due_date:
+ try:
+ self.grace_period = parse_timedelta(grace_period_string)
+ self.close_date = self.display_due_date + self.grace_period
+ except:
+ log.error("Error parsing the grace period {0}".format(grace_period_string))
+ raise
+ else:
+ self.grace_period = None
+ self.close_date = self.display_due_date
diff --git a/common/lib/xmodule/xmodule/x_module.py b/common/lib/xmodule/xmodule/x_module.py
index 07b9d3653e..b5e9b10ea6 100644
--- a/common/lib/xmodule/xmodule/x_module.py
+++ b/common/lib/xmodule/xmodule/x_module.py
@@ -879,7 +879,9 @@ class ModuleSystem(object):
xqueue=None,
node_path="",
anonymous_student_id='',
- course_id=None):
+ course_id=None,
+ open_ended_grading_interface=None,
+ s3_interface=None):
'''
Create a closure around the system environment.
@@ -930,6 +932,8 @@ class ModuleSystem(object):
self.anonymous_student_id = anonymous_student_id
self.course_id = course_id
self.user_is_staff = user is not None and user.is_staff
+ self.open_ended_grading_interface = open_ended_grading_interface
+ self.s3_interface = s3_interface
def get(self, attr):
''' provide uniform access to attributes (like etree).'''
diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py
index b52d473dc5..7877c83bdc 100644
--- a/lms/djangoapps/courseware/module_render.py
+++ b/lms/djangoapps/courseware/module_render.py
@@ -226,6 +226,30 @@ def _get_module(user, request, descriptor, student_module_cache, course_id,
'waittime': settings.XQUEUE_WAITTIME_BETWEEN_REQUESTS
}
+ def get_or_default(key, default):
+ getattr(settings, key, default)
+
+ #This is a hacky way to pass settings to the combined open ended xmodule
+ #It needs an S3 interface to upload images to S3
+ #It needs the open ended grading interface in order to get peer grading to be done
+ #TODO: refactor these settings into module-specific settings when possible.
+ #this first checks to see if the descriptor is the correct one, and only sends settings if it is
+ is_descriptor_combined_open_ended = (descriptor.__class__.__name__ == 'CombinedOpenEndedDescriptor')
+ is_descriptor_peer_grading = (descriptor.__class__.__name__ == 'PeerGradingDescriptor')
+ open_ended_grading_interface = None
+ s3_interface = None
+ if is_descriptor_combined_open_ended or is_descriptor_peer_grading:
+ open_ended_grading_interface = settings.OPEN_ENDED_GRADING_INTERFACE
+ open_ended_grading_interface['mock_peer_grading'] = settings.MOCK_PEER_GRADING
+ open_ended_grading_interface['mock_staff_grading'] = settings.MOCK_STAFF_GRADING
+ if is_descriptor_combined_open_ended:
+ s3_interface = {
+ 'access_key' : get_or_default('AWS_ACCESS_KEY_ID',''),
+ 'secret_access_key' : get_or_default('AWS_SECRET_ACCESS_KEY',''),
+ 'storage_bucket_name' : get_or_default('AWS_STORAGE_BUCKET_NAME','')
+ }
+
+
def inner_get_module(descriptor):
"""
Delegate to get_module. It does an access check, so may return None
@@ -255,6 +279,8 @@ def _get_module(user, request, descriptor, student_module_cache, course_id,
node_path=settings.NODE_PATH,
anonymous_student_id=unique_id_for_user(user),
course_id=course_id,
+ open_ended_grading_interface=open_ended_grading_interface,
+ s3_interface=s3_interface,
)
# pass position specified in URL to module through ModuleSystem
system.set('position', position)
diff --git a/lms/djangoapps/open_ended_grading/controller_query_service.py b/lms/djangoapps/open_ended_grading/controller_query_service.py
index 83d5617bd2..1b124fc116 100644
--- a/lms/djangoapps/open_ended_grading/controller_query_service.py
+++ b/lms/djangoapps/open_ended_grading/controller_query_service.py
@@ -1,12 +1,6 @@
-import json
import logging
-import requests
-from requests.exceptions import RequestException, ConnectionError, HTTPError
-import sys
-from xmodule.grading_service_module import GradingService, GradingServiceError
+from xmodule.open_ended_grading_classes.grading_service_module import GradingService
-from django.conf import settings
-from django.http import HttpResponse, Http404
from xmodule.x_module import ModuleSystem
from mitxmako.shortcuts import render_to_string
@@ -20,6 +14,8 @@ class ControllerQueryService(GradingService):
def __init__(self, config):
config['system'] = ModuleSystem(None, None, None, render_to_string, None)
super(ControllerQueryService, self).__init__(config)
+ self.url = config['url'] + config['grading_controller']
+ self.login_url = self.url + '/login/'
self.check_eta_url = self.url + '/get_submission_eta/'
self.is_unique_url = self.url + '/is_name_unique/'
self.combined_notifications_url = self.url + '/combined_notifications/'
diff --git a/lms/djangoapps/open_ended_grading/open_ended_notifications.py b/lms/djangoapps/open_ended_grading/open_ended_notifications.py
index 4055aab347..ecec29fce3 100644
--- a/lms/djangoapps/open_ended_grading/open_ended_notifications.py
+++ b/lms/djangoapps/open_ended_grading/open_ended_notifications.py
@@ -1,16 +1,14 @@
from django.conf import settings
+from xmodule.open_ended_grading_classes import peer_grading_service
from staff_grading_service import StaffGradingService
from open_ended_grading.controller_query_service import ControllerQueryService
-from xmodule import peer_grading_service
import json
from student.models import unique_id_for_user
-import open_ended_util
from courseware.models import StudentModule
import logging
from courseware.access import has_access
from util.cache import cache
import datetime
-from xmodule import peer_grading_service
from xmodule.x_module import ModuleSystem
from mitxmako.shortcuts import render_to_string
@@ -28,7 +26,7 @@ NOTIFICATION_TYPES = (
def staff_grading_notifications(course, user):
- staff_gs = StaffGradingService(settings.STAFF_GRADING_INTERFACE)
+ staff_gs = StaffGradingService(settings.OPEN_ENDED_GRADING_INTERFACE)
pending_grading = False
img_path = ""
course_id = course.id
@@ -61,7 +59,7 @@ def staff_grading_notifications(course, user):
def peer_grading_notifications(course, user):
system = ModuleSystem(None, None, None, render_to_string, None)
- peer_gs = peer_grading_service.PeerGradingService(settings.PEER_GRADING_INTERFACE, system)
+ peer_gs = peer_grading_service.PeerGradingService(settings.OPEN_ENDED_GRADING_INTERFACE, system)
pending_grading = False
img_path = ""
course_id = course.id
@@ -93,8 +91,7 @@ def peer_grading_notifications(course, user):
def combined_notifications(course, user):
- controller_url = open_ended_util.get_controller_url()
- controller_qs = ControllerQueryService(controller_url)
+ controller_qs = ControllerQueryService(settings.OPEN_ENDED_GRADING_INTERFACE)
student_id = unique_id_for_user(user)
user_is_staff = has_access(user, course, 'staff')
course_id = course.id
diff --git a/lms/djangoapps/open_ended_grading/open_ended_util.py b/lms/djangoapps/open_ended_grading/open_ended_util.py
deleted file mode 100644
index 1aa0f1ba70..0000000000
--- a/lms/djangoapps/open_ended_grading/open_ended_util.py
+++ /dev/null
@@ -1,13 +0,0 @@
-from django.conf import settings
-import logging
-
-log = logging.getLogger(__name__)
-
-
-def get_controller_url():
- peer_grading_url = settings.PEER_GRADING_INTERFACE['url']
- split_url = peer_grading_url.split("/")
- controller_url = "http://" + split_url[2] + "/grading_controller"
- controller_settings = settings.PEER_GRADING_INTERFACE.copy()
- controller_settings['url'] = controller_url
- return controller_settings
diff --git a/lms/djangoapps/open_ended_grading/staff_grading_service.py b/lms/djangoapps/open_ended_grading/staff_grading_service.py
index 274a25142d..0ead7aa364 100644
--- a/lms/djangoapps/open_ended_grading/staff_grading_service.py
+++ b/lms/djangoapps/open_ended_grading/staff_grading_service.py
@@ -4,10 +4,7 @@ This module provides views that proxy to the staff grading backend service.
import json
import logging
-import requests
-from requests.exceptions import RequestException, ConnectionError, HTTPError
-import sys
-from xmodule.grading_service_module import GradingService, GradingServiceError
+from xmodule.open_ended_grading_classes.grading_service_module import GradingService, GradingServiceError
from django.conf import settings
from django.http import HttpResponse, Http404
@@ -64,6 +61,8 @@ class StaffGradingService(GradingService):
def __init__(self, config):
config['system'] = ModuleSystem(None, None, None, render_to_string, None)
super(StaffGradingService, self).__init__(config)
+ self.url = config['url'] + config['staff_grading']
+ self.login_url = self.url + '/login/'
self.get_next_url = self.url + '/get_next_submission/'
self.save_grade_url = self.url + '/save_grade/'
self.get_problem_list_url = self.url + '/get_problem_list/'
@@ -164,7 +163,7 @@ def staff_grading_service():
if settings.MOCK_STAFF_GRADING:
_service = MockStaffGradingService()
else:
- _service = StaffGradingService(settings.STAFF_GRADING_INTERFACE)
+ _service = StaffGradingService(settings.OPEN_ENDED_GRADING_INTERFACE)
return _service
diff --git a/lms/djangoapps/open_ended_grading/tests.py b/lms/djangoapps/open_ended_grading/tests.py
index 9350832719..d452883ebb 100644
--- a/lms/djangoapps/open_ended_grading/tests.py
+++ b/lms/djangoapps/open_ended_grading/tests.py
@@ -6,7 +6,8 @@ django-admin.py test --settings=lms.envs.test --pythonpath=. lms/djangoapps/open
from django.test import TestCase
from open_ended_grading import staff_grading_service
-from xmodule import peer_grading_service, peer_grading_module
+from xmodule.open_ended_grading_classes import peer_grading_service
+from xmodule import peer_grading_module
from django.core.urlresolvers import reverse
from django.contrib.auth.models import Group
@@ -25,6 +26,8 @@ log = logging.getLogger(__name__)
from django.test.utils import override_settings
from django.http import QueryDict
+from xmodule.tests import test_util_open_ended
+
@override_settings(MODULESTORE=ct.TEST_DATA_XML_MODULESTORE)
class TestStaffGradingService(ct.PageLoader):
@@ -144,9 +147,11 @@ class TestPeerGradingService(ct.PageLoader):
location = "i4x://edX/toy/peergrading/init"
self.mock_service = peer_grading_service.MockPeerGradingService()
- self.system = ModuleSystem(location, None, None, render_to_string, None)
+ self.system = ModuleSystem(location, None, None, render_to_string, None,
+ s3_interface = test_util_open_ended.S3_INTERFACE,
+ open_ended_grading_interface=test_util_open_ended.OPEN_ENDED_GRADING_INTERFACE
+ )
self.descriptor = peer_grading_module.PeerGradingDescriptor(self.system)
-
self.peer_module = peer_grading_module.PeerGradingModule(self.system, location, "
| Problem Name | +Due date | Graded | Available | Required | @@ -22,7 +23,18 @@ %for problem in problem_list:
|---|---|---|---|---|
| - ${problem['problem_name']} + %if problem['closed']: + ${problem['problem_name']} + %else: + ${problem['problem_name']} + %endif + | ++ % if problem['due']: + ${problem['due']} + % else: + No due date + % endif |
${problem['num_graded']}
diff --git a/lms/templates/peer_grading/peer_grading_closed.html b/lms/templates/peer_grading/peer_grading_closed.html
new file mode 100644
index 0000000000..712ad8b380
--- /dev/null
+++ b/lms/templates/peer_grading/peer_grading_closed.html
@@ -0,0 +1,10 @@
+Peer Grading+The due date has passed, and + % if use_for_single_location: + peer grading for this problem is closed at this time. + %else: + peer grading is closed at this time. + %endif + + |