Merge branch 'master' into diana/fix-self-assessment-test
This commit is contained in:
@@ -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
|
||||
return elt
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
__author__ = 'vik'
|
||||
@@ -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
|
||||
return elt
|
||||
@@ -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']
|
||||
|
||||
@@ -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)
|
||||
@@ -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):
|
||||
"""
|
||||
@@ -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.")
|
||||
|
||||
@@ -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
|
||||
@@ -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):
|
||||
"""
|
||||
@@ -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,
|
||||
|
||||
@@ -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('''
|
||||
|
||||
@@ -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,
|
||||
|
||||
14
common/lib/xmodule/xmodule/tests/test_util_open_ended.py
Normal file
14
common/lib/xmodule/xmodule/tests/test_util_open_ended.py
Normal file
@@ -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" : "",
|
||||
}
|
||||
39
common/lib/xmodule/xmodule/timeinfo.py
Normal file
39
common/lib/xmodule/xmodule/timeinfo.py
Normal file
@@ -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
|
||||
@@ -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).'''
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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/'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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, "<peergrading/>", self.descriptor)
|
||||
self.peer_module.peer_gs = self.mock_service
|
||||
self.logout()
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import logging
|
||||
import urllib
|
||||
import re
|
||||
|
||||
from django.conf import settings
|
||||
from django.views.decorators.cache import cache_control
|
||||
@@ -13,12 +12,10 @@ from student.models import unique_id_for_user
|
||||
from courseware.courses import get_course_with_access
|
||||
|
||||
from controller_query_service import ControllerQueryService
|
||||
from xmodule.grading_service_module import GradingServiceError
|
||||
from xmodule.open_ended_grading_classes.grading_service_module import GradingServiceError
|
||||
import json
|
||||
from .staff_grading import StaffGrading
|
||||
from student.models import unique_id_for_user
|
||||
|
||||
import open_ended_util
|
||||
import open_ended_notifications
|
||||
|
||||
from xmodule.modulestore.django import modulestore
|
||||
@@ -30,8 +27,7 @@ log = logging.getLogger(__name__)
|
||||
|
||||
template_imports = {'urllib': urllib}
|
||||
|
||||
controller_url = open_ended_util.get_controller_url()
|
||||
controller_qs = ControllerQueryService(controller_url)
|
||||
controller_qs = ControllerQueryService(settings.OPEN_ENDED_GRADING_INTERFACE)
|
||||
|
||||
"""
|
||||
Reverses the URL from the name and the course id, and then adds a trailing slash if
|
||||
|
||||
@@ -100,10 +100,7 @@ XQUEUE_INTERFACE = AUTH_TOKENS['XQUEUE_INTERFACE']
|
||||
MODULESTORE = AUTH_TOKENS.get('MODULESTORE', MODULESTORE)
|
||||
CONTENTSTORE = AUTH_TOKENS.get('CONTENTSTORE', CONTENTSTORE)
|
||||
|
||||
STAFF_GRADING_INTERFACE = AUTH_TOKENS.get('STAFF_GRADING_INTERFACE',
|
||||
STAFF_GRADING_INTERFACE)
|
||||
PEER_GRADING_INTERFACE = AUTH_TOKENS.get('PEER_GRADING_INTERFACE',
|
||||
PEER_GRADING_INTERFACE)
|
||||
OPEN_ENDED_GRADING_INTERFACE = AUTH_TOKENS.get('OPEN_ENDED_GRADING_INTERFACE', OPEN_ENDED_GRADING_INTERFACE)
|
||||
|
||||
PEARSON_TEST_USER = "pearsontest"
|
||||
PEARSON_TEST_PASSWORD = AUTH_TOKENS.get("PEARSON_TEST_PASSWORD")
|
||||
|
||||
@@ -310,37 +310,30 @@ WIKI_USE_BOOTSTRAP_SELECT_WIDGET = False
|
||||
WIKI_LINK_LIVE_LOOKUPS = False
|
||||
WIKI_LINK_DEFAULT_LEVEL = 2
|
||||
|
||||
################################# Staff grading config #####################
|
||||
|
||||
#By setting up the default settings with an incorrect user name and password,
|
||||
# will get an error when attempting to connect
|
||||
STAFF_GRADING_INTERFACE = {
|
||||
'url': 'http://sandbox-grader-001.m.edx.org/staff_grading',
|
||||
'username': 'incorrect_user',
|
||||
'password': 'incorrect_pass',
|
||||
}
|
||||
|
||||
# Used for testing, debugging
|
||||
MOCK_STAFF_GRADING = False
|
||||
|
||||
################################# Pearson TestCenter config ################
|
||||
|
||||
PEARSONVUE_SIGNINPAGE_URL = "https://www1.pearsonvue.com/testtaker/signin/SignInPage/EDX"
|
||||
# TESTCENTER_ACCOMMODATION_REQUEST_EMAIL = "exam-help@edx.org"
|
||||
|
||||
################################# Peer grading config #####################
|
||||
################################# open ended grading config #####################
|
||||
|
||||
#By setting up the default settings with an incorrect user name and password,
|
||||
# will get an error when attempting to connect
|
||||
PEER_GRADING_INTERFACE = {
|
||||
OPEN_ENDED_GRADING_INTERFACE = {
|
||||
'url': 'http://sandbox-grader-001.m.edx.org/peer_grading',
|
||||
'username': 'incorrect_user',
|
||||
'password': 'incorrect_pass',
|
||||
'staff_grading' : 'staff_grading',
|
||||
'peer_grading' : 'peer_grading',
|
||||
'grading_controller' : 'grading_controller'
|
||||
}
|
||||
|
||||
# Used for testing, debugging
|
||||
# Used for testing, debugging peer grading
|
||||
MOCK_PEER_GRADING = False
|
||||
|
||||
# Used for testing, debugging staff grading
|
||||
MOCK_STAFF_GRADING = False
|
||||
|
||||
################################# Jasmine ###################################
|
||||
JASMINE_TEST_DIRECTORY = PROJECT_ROOT + '/static/coffee'
|
||||
|
||||
|
||||
@@ -131,21 +131,17 @@ if os.path.isdir(DATA_DIR):
|
||||
|
||||
MITX_VERSION_STRING = os.popen('cd %s; git describe' % REPO_ROOT).read().strip()
|
||||
|
||||
################################# Staff grading config #####################
|
||||
################################# Open ended grading config #####################
|
||||
|
||||
STAFF_GRADING_INTERFACE = {
|
||||
'url': 'http://127.0.0.1:3033/staff_grading',
|
||||
'username': 'lms',
|
||||
'password': 'abcd',
|
||||
}
|
||||
OPEN_ENDED_GRADING_INTERFACE = {
|
||||
'url' : 'http://127.0.0.1:3033/',
|
||||
'username' : 'lms',
|
||||
'password' : 'abcd',
|
||||
'staff_grading' : 'staff_grading',
|
||||
'peer_grading' : 'peer_grading',
|
||||
'grading_controller' : 'grading_controller'
|
||||
}
|
||||
|
||||
################################# Peer grading config #####################
|
||||
|
||||
PEER_GRADING_INTERFACE = {
|
||||
'url': 'http://127.0.0.1:3033/peer_grading',
|
||||
'username': 'lms',
|
||||
'password': 'abcd',
|
||||
}
|
||||
################################ LMS Migration #################################
|
||||
MITX_FEATURES['ENABLE_LMS_MIGRATION'] = True
|
||||
MITX_FEATURES['ACCESS_REQUIRE_STAFF_FOR_COURSE'] = False # require that user be in the staff_* group to be able to enroll
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
<table class="problem-list">
|
||||
<tr>
|
||||
<th>Problem Name</th>
|
||||
<th>Due date</th>
|
||||
<th>Graded</th>
|
||||
<th>Available</th>
|
||||
<th>Required</th>
|
||||
@@ -22,7 +23,18 @@
|
||||
%for problem in problem_list:
|
||||
<tr data-graded="${problem['num_graded']}" data-required="${problem['num_required']}">
|
||||
<td class="problem-name">
|
||||
<a href="#problem" data-location="${problem['location']}" class="problem-button">${problem['problem_name']}</a>
|
||||
%if problem['closed']:
|
||||
${problem['problem_name']}
|
||||
%else:
|
||||
<a href="#problem" data-location="${problem['location']}" class="problem-button">${problem['problem_name']}</a>
|
||||
%endif
|
||||
</td>
|
||||
<td>
|
||||
% if problem['due']:
|
||||
${problem['due']}
|
||||
% else:
|
||||
No due date
|
||||
% endif
|
||||
</td>
|
||||
<td>
|
||||
${problem['num_graded']}
|
||||
|
||||
10
lms/templates/peer_grading/peer_grading_closed.html
Normal file
10
lms/templates/peer_grading/peer_grading_closed.html
Normal file
@@ -0,0 +1,10 @@
|
||||
<section class="container peer-grading-container">
|
||||
<h2>Peer Grading</h2>
|
||||
<p>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
|
||||
</p>
|
||||
</section>
|
||||
Reference in New Issue
Block a user