Commit 7a326cd5 by Calen Pennington

Move I4xSystem into xmodule so that the definition is available to the CMS

parent b0568294
...@@ -80,7 +80,7 @@ class LoncapaProblem(object): ...@@ -80,7 +80,7 @@ class LoncapaProblem(object):
- id (string): identifier for this problem; often a filename (no spaces) - id (string): identifier for this problem; often a filename (no spaces)
- state (dict): student state - state (dict): student state
- seed (int): random number generator seed (int) - seed (int): random number generator seed (int)
- system (I4xSystem): I4xSystem instance which provides OS, rendering, and user context - system (ModuleSystem): ModuleSystem instance which provides OS, rendering, and user context
''' '''
...@@ -286,7 +286,7 @@ class LoncapaProblem(object): ...@@ -286,7 +286,7 @@ class LoncapaProblem(object):
file = inc.get('file') file = inc.get('file')
if file is not None: if file is not None:
try: try:
ifp = self.system.filestore.open(file) # open using I4xSystem OSFS filestore ifp = self.system.filestore.open(file) # open using ModuleSystem OSFS filestore
except Exception as err: except Exception as err:
log.error('Error %s in problem xml include: %s' % (err, etree.tostring(inc, pretty_print=True))) log.error('Error %s in problem xml include: %s' % (err, etree.tostring(inc, pretty_print=True)))
log.error('Cannot find file %s in %s' % (file, self.system.filestore)) log.error('Cannot find file %s in %s' % (file, self.system.filestore))
......
...@@ -50,7 +50,7 @@ class SimpleInput():# XModule ...@@ -50,7 +50,7 @@ class SimpleInput():# XModule
''' '''
Instantiate a SimpleInput class. Arguments: Instantiate a SimpleInput class. Arguments:
- system : I4xSystem instance which provides OS, rendering, and user context - system : ModuleSystem instance which provides OS, rendering, and user context
- xml : Element tree of this Input element - xml : Element tree of this Input element
- item_id : id for this input element (assigned by capa_problem.LoncapProblem) - string - item_id : id for this input element (assigned by capa_problem.LoncapProblem) - string
- track_url : URL used for tracking - string - track_url : URL used for tracking - string
......
...@@ -101,7 +101,7 @@ class LoncapaResponse(object): ...@@ -101,7 +101,7 @@ class LoncapaResponse(object):
- xml : ElementTree of this Response - xml : ElementTree of this Response
- inputfields : ordered list of ElementTrees for each input entry field in this Response - inputfields : ordered list of ElementTrees for each input entry field in this Response
- context : script processor context - context : script processor context
- system : I4xSystem instance which provides OS, rendering, and user context - system : ModuleSystem instance which provides OS, rendering, and user context
''' '''
self.xml = xml self.xml = xml
......
...@@ -16,33 +16,23 @@ import capa.calc as calc ...@@ -16,33 +16,23 @@ import capa.calc as calc
import capa.capa_problem as lcp import capa.capa_problem as lcp
from capa.correctmap import CorrectMap from capa.correctmap import CorrectMap
from xmodule import graders, x_module from xmodule import graders, x_module
from xmodule.x_module import ModuleSystem
from xmodule.graders import Score, aggregate_scores from xmodule.graders import Score, aggregate_scores
from xmodule.progress import Progress from xmodule.progress import Progress
from nose.plugins.skip import SkipTest from nose.plugins.skip import SkipTest
from mock import Mock
class I4xSystem(object): i4xs = ModuleSystem(
''' ajax_url='/',
This is an abstraction such that x_modules can function independent track_function=Mock(),
of the courseware (e.g. import into other types of courseware, LMS, get_module=Mock(),
or if we want to have a sandbox server for user-contributed content) render_template=Mock(),
''' replace_urls=Mock(),
def __init__(self): user=Mock(),
self.ajax_url = '/' filestore=fs.osfs.OSFS(os.path.dirname(os.path.realpath(__file__))),
self.track_function = lambda x: None debug=True,
self.filestore = fs.osfs.OSFS(os.path.dirname(os.path.realpath(__file__))) xqueue_callback_url='/'
self.render_function = lambda x: {} # Probably incorrect )
self.module_from_xml = lambda x: None # May need a real impl...
self.exception404 = Exception
self.DEBUG = True
def __repr__(self):
return repr(self.__dict__)
def __str__(self):
return str(self.__dict__)
i4xs = I4xSystem()
class ModelsTest(unittest.TestCase): class ModelsTest(unittest.TestCase):
......
...@@ -5,14 +5,13 @@ import json ...@@ -5,14 +5,13 @@ import json
import logging import logging
import traceback import traceback
import re import re
import StringIO
import os
from datetime import timedelta from datetime import timedelta
from lxml import etree from lxml import etree
from xmodule.x_module import XModule from xmodule.x_module import XModule
from xmodule.raw_module import RawDescriptor from xmodule.raw_module import RawDescriptor
from xmodule.exceptions import NotFoundError
from progress import Progress from progress import Progress
from capa.capa_problem import LoncapaProblem from capa.capa_problem import LoncapaProblem
from capa.responsetypes import StudentInputError from capa.responsetypes import StudentInputError
...@@ -319,8 +318,8 @@ class CapaModule(XModule): ...@@ -319,8 +318,8 @@ class CapaModule(XModule):
if self.show_answer == 'always': if self.show_answer == 'always':
return True return True
#TODO: Not 404
raise self.system.exception404 return False
def update_score(self, get): def update_score(self, get):
""" """
...@@ -347,7 +346,7 @@ class CapaModule(XModule): ...@@ -347,7 +346,7 @@ class CapaModule(XModule):
Returns the answers: {'answers' : answers} Returns the answers: {'answers' : answers}
''' '''
if not self.answer_available(): if not self.answer_available():
raise self.system.exception404 raise NotFoundError('Answer is not available')
else: else:
answers = self.lcp.get_question_answers() answers = self.lcp.get_question_answers()
return {'answers': answers} return {'answers': answers}
...@@ -403,15 +402,14 @@ class CapaModule(XModule): ...@@ -403,15 +402,14 @@ class CapaModule(XModule):
if self.closed(): if self.closed():
event_info['failure'] = 'closed' event_info['failure'] = 'closed'
self.system.track_function('save_problem_check_fail', event_info) self.system.track_function('save_problem_check_fail', event_info)
# TODO (vshnayder): probably not 404? raise NotFoundError('Problem is closed')
raise self.system.exception404
# Problem submitted. Student should reset before checking # Problem submitted. Student should reset before checking
# again. # again.
if self.lcp.done and self.rerandomize == "always": if self.lcp.done and self.rerandomize == "always":
event_info['failure'] = 'unreset' event_info['failure'] = 'unreset'
self.system.track_function('save_problem_check_fail', event_info) self.system.track_function('save_problem_check_fail', event_info)
raise self.system.exception404 raise NotFoundError('Problem must be reset before it can be checked again')
try: try:
old_state = self.lcp.get_state() old_state = self.lcp.get_state()
......
...@@ -4,7 +4,6 @@ import logging ...@@ -4,7 +4,6 @@ import logging
from xmodule.modulestore import Location from xmodule.modulestore import Location
from xmodule.seq_module import SequenceDescriptor, SequenceModule from xmodule.seq_module import SequenceDescriptor, SequenceModule
from fs.errors import ResourceNotFoundError
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
......
class InvalidDefinitionError(Exception): class InvalidDefinitionError(Exception):
pass pass
class NotFoundError(Exception):
pass
...@@ -7,6 +7,7 @@ from xmodule.mako_module import MakoModuleDescriptor ...@@ -7,6 +7,7 @@ from xmodule.mako_module import MakoModuleDescriptor
from xmodule.xml_module import XmlDescriptor from xmodule.xml_module import XmlDescriptor
from xmodule.x_module import XModule from xmodule.x_module import XModule
from xmodule.progress import Progress from xmodule.progress import Progress
from xmodule.exceptions import NotFoundError
log = logging.getLogger("mitx.common.lib.seq_module") log = logging.getLogger("mitx.common.lib.seq_module")
...@@ -56,7 +57,7 @@ class SequenceModule(XModule): ...@@ -56,7 +57,7 @@ class SequenceModule(XModule):
if dispatch == 'goto_position': if dispatch == 'goto_position':
self.position = int(get['position']) self.position = int(get['position'])
return json.dumps({'success': True}) return json.dumps({'success': True})
raise self.system.exception404 raise NotFoundError('Unexpected dispatch type')
def render(self): def render(self):
if self.rendered: if self.rendered:
......
...@@ -81,7 +81,7 @@ class XModule(object): ...@@ -81,7 +81,7 @@ class XModule(object):
''' '''
Construct a new xmodule Construct a new xmodule
system: An I4xSystem allowing access to external resources system: A ModuleSystem allowing access to external resources
location: Something Location-like that identifies this xmodule location: Something Location-like that identifies this xmodule
definition: A dictionary containing 'data' and 'children'. Both are optional definition: A dictionary containing 'data' and 'children'. Both are optional
'data': is JSON-like (string, dictionary, list, bool, or None, optionally nested). 'data': is JSON-like (string, dictionary, list, bool, or None, optionally nested).
...@@ -452,3 +452,62 @@ class XMLParsingSystem(DescriptorSystem): ...@@ -452,3 +452,62 @@ class XMLParsingSystem(DescriptorSystem):
""" """
DescriptorSystem.__init__(self, load_item, resources_fs) DescriptorSystem.__init__(self, load_item, resources_fs)
self.process_xml = process_xml self.process_xml = process_xml
class ModuleSystem(object):
'''
This is an abstraction such that x_modules can function independent
of the courseware (e.g. import into other types of courseware, LMS,
or if we want to have a sandbox server for user-contributed content)
ModuleSystem objects are passed to x_modules to provide access to system
functionality.
Note that these functions can be closures over e.g. a django request
and user, or other environment-specific info.
'''
def __init__(self, ajax_url, track_function,
get_module, render_template, replace_urls,
user=None, filestore=None, debug=False, xqueue_callback_url=None):
'''
Create a closure around the system environment.
ajax_url - the url where ajax calls to the encapsulating module go.
track_function - function of (event_type, event), intended for logging
or otherwise tracking the event.
TODO: Not used, and has inconsistent args in different
files. Update or remove.
get_module - function that takes (location) and returns a corresponding
module instance object.
render_template - a function that takes (template_file, context), and returns
rendered html.
user - The user to base the random number generator seed off of for this request
filestore - A filestore ojbect. Defaults to an instance of OSFS based at
settings.DATA_DIR.
replace_urls - TEMPORARY - A function like static_replace.replace_urls
that capa_module can use to fix up the static urls in ajax results.
'''
self.ajax_url = ajax_url
self.xqueue_callback_url = xqueue_callback_url
self.track_function = track_function
self.filestore = filestore
self.get_module = get_module
self.render_template = render_template
self.DEBUG = self.debug = debug
self.seed = user.id if user is not None else 0
self.replace_urls = replace_urls
def get(self, attr):
''' provide uniform access to attributes (like etree).'''
return self.__dict__.get(attr)
def set(self, attr, val):
'''provide uniform access to attributes (like etree)'''
self.__dict__[attr] = val
def __repr__(self):
return repr(self.__dict__)
def __str__(self):
return str(self.__dict__)
...@@ -12,75 +12,16 @@ from xmodule.modulestore.django import modulestore ...@@ -12,75 +12,16 @@ from xmodule.modulestore.django import modulestore
from mitxmako.shortcuts import render_to_string from mitxmako.shortcuts import render_to_string
from models import StudentModule, StudentModuleCache from models import StudentModule, StudentModuleCache
from static_replace import replace_urls from static_replace import replace_urls
from xmodule.exceptions import NotFoundError
from xmodule.x_module import ModuleSystem
log = logging.getLogger("mitx.courseware") log = logging.getLogger("mitx.courseware")
class I4xSystem(object):
'''
This is an abstraction such that x_modules can function independent
of the courseware (e.g. import into other types of courseware, LMS,
or if we want to have a sandbox server for user-contributed content)
I4xSystem objects are passed to x_modules to provide access to system
functionality.
Note that these functions can be closures over e.g. a django request
and user, or other environment-specific info.
'''
def __init__(self, ajax_url, track_function,
get_module, render_template, replace_urls,
user=None, filestore=None, xqueue_callback_url=None):
'''
Create a closure around the system environment.
ajax_url - the url where ajax calls to the encapsulating module go.
xqueue_callback_url - the url where external queueing system (e.g. for grading)
returns its response
track_function - function of (event_type, event), intended for logging
or otherwise tracking the event.
TODO: Not used, and has inconsistent args in different
files. Update or remove.
get_module - function that takes (location) and returns a corresponding
module instance object.
render_template - a function that takes (template_file, context), and returns
rendered html.
user - The user to base the random number generator seed off of for this request
filestore - A filestore ojbect. Defaults to an instance of OSFS based at
settings.DATA_DIR.
replace_urls - TEMPORARY - A function like static_replace.replace_urls
that capa_module can use to fix up the static urls in ajax results.
'''
self.ajax_url = ajax_url
self.xqueue_callback_url = xqueue_callback_url
self.track_function = track_function
self.filestore = filestore
self.get_module = get_module
self.render_template = render_template
self.exception404 = Http404
self.DEBUG = settings.DEBUG
self.seed = user.id if user is not None else 0
self.replace_urls = replace_urls
def get(self, attr):
''' provide uniform access to attributes (like etree).'''
return self.__dict__.get(attr)
def set(self, attr, val):
'''provide uniform access to attributes (like etree)'''
self.__dict__[attr] = val
def __repr__(self):
return repr(self.__dict__)
def __str__(self):
return str(self.__dict__)
def make_track_function(request): def make_track_function(request):
''' '''
Make a tracking function that logs what happened. Make a tracking function that logs what happened.
For use in I4xSystem. For use in ModuleSystem.
''' '''
import track.views import track.views
...@@ -221,20 +162,20 @@ def get_module(user, request, location, student_module_cache, position=None): ...@@ -221,20 +162,20 @@ def get_module(user, request, location, student_module_cache, position=None):
# TODO (cpennington): When modules are shared between courses, the static # TODO (cpennington): When modules are shared between courses, the static
# prefix is going to have to be specific to the module, not the directory # prefix is going to have to be specific to the module, not the directory
# that the xml was loaded from # that the xml was loaded from
system = I4xSystem(track_function=make_track_function(request), system = ModuleSystem(track_function=make_track_function(request),
render_template=render_to_string, render_template=render_to_string,
ajax_url=ajax_url, ajax_url=ajax_url,
xqueue_callback_url=xqueue_callback_url, xqueue_callback_url=xqueue_callback_url,
# TODO (cpennington): Figure out how to share info between systems # TODO (cpennington): Figure out how to share info between systems
filestore=descriptor.system.resources_fs, filestore=descriptor.system.resources_fs,
get_module=_get_module, get_module=_get_module,
user=user, user=user,
# TODO (cpennington): This should be removed when all html from # TODO (cpennington): This should be removed when all html from
# a module is coming through get_html and is therefore covered # a module is coming through get_html and is therefore covered
# by the replace_static_urls code below # by the replace_static_urls code below
replace_urls=replace_urls, replace_urls=replace_urls,
) )
# pass position specified in URL to module through I4xSystem # pass position specified in URL to module through ModuleSystem
system.set('position', position) system.set('position', position)
module = descriptor.xmodule_constructor(system)(instance_state, shared_state) module = descriptor.xmodule_constructor(system)(instance_state, shared_state)
...@@ -407,6 +348,9 @@ def modx_dispatch(request, dispatch=None, id=None): ...@@ -407,6 +348,9 @@ def modx_dispatch(request, dispatch=None, id=None):
# Let the module handle the AJAX # Let the module handle the AJAX
try: try:
ajax_return = instance.handle_ajax(dispatch, request.POST) ajax_return = instance.handle_ajax(dispatch, request.POST)
except NotFoundError:
log.exception("Module indicating to user that request doesn't exist")
raise Http404
except: except:
log.exception("error processing ajax call") log.exception("error processing ajax call")
raise raise
......
...@@ -25,7 +25,7 @@ import track.views ...@@ -25,7 +25,7 @@ import track.views
from lxml import etree from lxml import etree
from courseware.module_render import make_track_function, I4xSystem, get_module from courseware.module_render import make_track_function, ModuleSystem, get_module
from courseware.models import StudentModule from courseware.models import StudentModule
from multicourse import multicourse_settings from multicourse import multicourse_settings
from student.models import UserProfile from student.models import UserProfile
...@@ -206,13 +206,12 @@ def quickedit(request, id=None, qetemplate='quickedit.html', coursename=None): ...@@ -206,13 +206,12 @@ def quickedit(request, id=None, qetemplate='quickedit.html', coursename=None):
ajax_url = settings.MITX_ROOT_URL + '/modx/' + id + '/' ajax_url = settings.MITX_ROOT_URL + '/modx/' + id + '/'
# Create the module (instance of capa_module.Module) # Create the module (instance of capa_module.Module)
system = I4xSystem(track_function=make_track_function(request), system = ModuleSystem(track_function=make_track_function(request),
render_function=None, render_function=None,
render_template=render_to_string, render_template=render_to_string,
ajax_url=ajax_url, ajax_url=ajax_url,
filestore=OSFS(settings.DATA_DIR + xp), filestore=OSFS(settings.DATA_DIR + xp),
#role = 'staff' if request.user.is_staff else 'student', # TODO: generalize this )
)
instance = xmodule.get_module_class(module)(system, instance = xmodule.get_module_class(module)(system,
xml, xml,
id, id,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment