Commit 969e001a by David Ormsbee

Merge pull request #38 from MITx/pmitros/xmod-reuse-refactor

Pmitros/xmod reuse refactor
parents b7fb999d bf625d1a
...@@ -53,13 +53,12 @@ html_special_response = {"textline":textline.render, ...@@ -53,13 +53,12 @@ html_special_response = {"textline":textline.render,
"schematic":schematic.render} "schematic":schematic.render}
class LoncapaProblem(object): class LoncapaProblem(object):
def __init__(self, filename, id=None, state=None, seed=None): def __init__(self, fileobject, id=None, state=None, seed=None):
## Initialize class variables from state ## Initialize class variables from state
self.seed = None self.seed = None
self.student_answers = dict() self.student_answers = dict()
self.correct_map = dict() self.correct_map = dict()
self.done = False self.done = False
self.filename = filename
if seed != None: if seed != None:
self.seed = seed self.seed = seed
...@@ -69,7 +68,6 @@ class LoncapaProblem(object): ...@@ -69,7 +68,6 @@ class LoncapaProblem(object):
else: else:
print "NO ID" print "NO ID"
raise Exception("This should never happen (183)") raise Exception("This should never happen (183)")
#self.problem_id = filename
if state: if state:
if 'seed' in state: if 'seed' in state:
...@@ -81,17 +79,12 @@ class LoncapaProblem(object): ...@@ -81,17 +79,12 @@ class LoncapaProblem(object):
if 'done' in state: if 'done' in state:
self.done = state['done'] self.done = state['done']
# print self.seed
# TODO: Does this deplete the Linux entropy pool? Is this fast enough? # TODO: Does this deplete the Linux entropy pool? Is this fast enough?
if not self.seed: if not self.seed:
self.seed=struct.unpack('i', os.urandom(4))[0] self.seed=struct.unpack('i', os.urandom(4))[0]
# print filename, self.seed, seed
## Parse XML file ## Parse XML file
#log.debug(u"LoncapaProblem() opening file {0}".format(filename)) file_text = fileobject.read()
file_text = open(filename).read()
# Convert startouttext and endouttext to proper <text></text> # Convert startouttext and endouttext to proper <text></text>
# TODO: Do with XML operations # TODO: Do with XML operations
file_text = re.sub("startouttext\s*/","text",file_text) file_text = re.sub("startouttext\s*/","text",file_text)
......
...@@ -18,8 +18,12 @@ from django.http import HttpResponse ...@@ -18,8 +18,12 @@ from django.http import HttpResponse
from django.shortcuts import redirect from django.shortcuts import redirect
from django.template import Context from django.template import Context
from django.template import Context, loader from django.template import Context, loader
from fs.osfs import OSFS
from mitxmako.shortcuts import render_to_response, render_to_string from mitxmako.shortcuts import render_to_response, render_to_string
from models import StudentModule from models import StudentModule
from student.models import UserProfile from student.models import UserProfile
import track.views import track.views
...@@ -30,6 +34,14 @@ import courseware.modules ...@@ -30,6 +34,14 @@ import courseware.modules
log = logging.getLogger("mitx.courseware") log = logging.getLogger("mitx.courseware")
class I4xSystem(object):
def __init__(self, ajax_url, track_function, render_function, filestore=None):
self.ajax_url = ajax_url
self.track_function = track_function
self.filestore = OSFS(settings.DATA_DIR)
self.render_function = render_function
self.exception404 = Http404
def object_cache(cache, user, module_type, module_id): def object_cache(cache, user, module_type, module_id):
# We don't look up on user -- all queries include user # We don't look up on user -- all queries include user
# Additional lookup would require a DB hit the way Django # Additional lookup would require a DB hit the way Django
...@@ -76,12 +88,15 @@ def modx_dispatch(request, module=None, dispatch=None, id=None): ...@@ -76,12 +88,15 @@ def modx_dispatch(request, module=None, dispatch=None, id=None):
xml = content_parser.module_xml(request.user, module, 'id', id) xml = content_parser.module_xml(request.user, module, 'id', id)
# Create the module # Create the module
instance=courseware.modules.get_module_class(module)(xml, system = I4xSystem(track_function = make_track_function(request),
render_function = None,
ajax_url = ajax_url,
filestore = None
)
instance=courseware.modules.get_module_class(module)(system,
xml,
id, id,
ajax_url=ajax_url, state=oldstate)
state=oldstate,
track_function = make_track_function(request),
render_function = None)
# Let the module handle the AJAX # Let the module handle the AJAX
ajax_return=instance.handle_ajax(dispatch, request.POST) ajax_return=instance.handle_ajax(dispatch, request.POST)
# Save the state back to the database # Save the state back to the database
...@@ -128,12 +143,15 @@ def render_x_module(user, request, xml_module, module_object_preload): ...@@ -128,12 +143,15 @@ def render_x_module(user, request, xml_module, module_object_preload):
# Create a new instance # Create a new instance
ajax_url = '/modx/'+module_type+'/'+module_id+'/' ajax_url = '/modx/'+module_type+'/'+module_id+'/'
instance=module_class(etree.tostring(xml_module), system = I4xSystem(track_function = make_track_function(request),
render_function = lambda x: render_module(user, request, x, module_object_preload),
ajax_url = ajax_url,
filestore = None
)
instance=module_class(system,
etree.tostring(xml_module),
module_id, module_id,
ajax_url=ajax_url, state=state)
state=state,
track_function = make_track_function(request),
render_function = lambda x: render_module(user, request, x, module_object_preload))
# If instance wasn't already in the database, create it # If instance wasn't already in the database, create it
if not smod: if not smod:
......
...@@ -16,9 +16,7 @@ import traceback ...@@ -16,9 +16,7 @@ import traceback
from lxml import etree from lxml import etree
## TODO: Abstract out from Django ## TODO: Abstract out from Django
from django.conf import settings from mitxmako.shortcuts import render_to_string
from mitxmako.shortcuts import render_to_response, render_to_string
from django.http import Http404
from x_module import XModule from x_module import XModule
from courseware.capa.capa_problem import LoncapaProblem, StudentInputError from courseware.capa.capa_problem import LoncapaProblem, StudentInputError
...@@ -92,7 +90,6 @@ class Module(XModule): ...@@ -92,7 +90,6 @@ class Module(XModule):
# User submitted a problem, and hasn't reset. We don't want # User submitted a problem, and hasn't reset. We don't want
# more submissions. # more submissions.
if self.lcp.done and self.rerandomize == "always": if self.lcp.done and self.rerandomize == "always":
#print "!"
check_button = False check_button = False
save_button = False save_button = False
...@@ -131,8 +128,8 @@ class Module(XModule): ...@@ -131,8 +128,8 @@ class Module(XModule):
return html return html
def __init__(self, xml, item_id, ajax_url=None, track_url=None, state=None, track_function=None, render_function = None, meta = None): def __init__(self, system, xml, item_id, state=None):
XModule.__init__(self, xml, item_id, ajax_url, track_url, state, track_function, render_function) XModule.__init__(self, system, xml, item_id, state)
self.attempts = 0 self.attempts = 0
self.max_attempts = None self.max_attempts = None
...@@ -185,15 +182,14 @@ class Module(XModule): ...@@ -185,15 +182,14 @@ class Module(XModule):
if state!=None and 'attempts' in state: if state!=None and 'attempts' in state:
self.attempts=state['attempts'] self.attempts=state['attempts']
self.filename=content_parser.item(dom2.xpath('/problem/@filename')) self.filename="problems/"+content_parser.item(dom2.xpath('/problem/@filename'))+".xml"
filename=settings.DATA_DIR+"/problems/"+self.filename+".xml"
self.name=content_parser.item(dom2.xpath('/problem/@name')) self.name=content_parser.item(dom2.xpath('/problem/@name'))
self.weight=content_parser.item(dom2.xpath('/problem/@weight')) self.weight=content_parser.item(dom2.xpath('/problem/@weight'))
if self.rerandomize == 'never': if self.rerandomize == 'never':
seed = 1 seed = 1
else: else:
seed = None seed = None
self.lcp=LoncapaProblem(filename, self.item_id, state, seed = seed) self.lcp=LoncapaProblem(self.filestore.open(self.filename), self.item_id, state, seed = seed)
def handle_ajax(self, dispatch, get): def handle_ajax(self, dispatch, get):
if dispatch=='problem_get': if dispatch=='problem_get':
...@@ -242,16 +238,15 @@ class Module(XModule): ...@@ -242,16 +238,15 @@ class Module(XModule):
if self.show_answer == 'closed' and not self.closed(): if self.show_answer == 'closed' and not self.closed():
return False return False
print "aa", self.show_answer print "aa", self.show_answer
raise Http404 raise self.system.exception404 #TODO: Not 404
def get_answer(self, get): def get_answer(self, get):
if not self.answer_available(): if not self.answer_available():
raise Http404 raise self.system.exception404
else: else:
return json.dumps(self.lcp.get_question_answers(), return json.dumps(self.lcp.get_question_answers(),
cls=ComplexEncoder) cls=ComplexEncoder)
# Figure out if we should move these to capa_problem? # Figure out if we should move these to capa_problem?
def get_problem(self, get): def get_problem(self, get):
''' Same as get_problem_html -- if we want to reconfirm we ''' Same as get_problem_html -- if we want to reconfirm we
...@@ -270,41 +265,33 @@ class Module(XModule): ...@@ -270,41 +265,33 @@ class Module(XModule):
for key in get: for key in get:
answers['_'.join(key.split('_')[1:])]=get[key] answers['_'.join(key.split('_')[1:])]=get[key]
# print "XXX", answers, get
event_info['answers']=answers event_info['answers']=answers
# Too late. Cannot submit # Too late. Cannot submit
if self.closed(): if self.closed():
event_info['failure']='closed' event_info['failure']='closed'
self.tracker('save_problem_check_fail', event_info) self.tracker('save_problem_check_fail', event_info)
print "cp" raise self.system.exception404
raise Http404
# 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.tracker('save_problem_check_fail', event_info) self.tracker('save_problem_check_fail', event_info)
print "cpdr" raise self.system.exception404
raise Http404
try: try:
old_state = self.lcp.get_state() old_state = self.lcp.get_state()
lcp_id = self.lcp.problem_id lcp_id = self.lcp.problem_id
filename = self.lcp.filename
correct_map = self.lcp.grade_answers(answers) correct_map = self.lcp.grade_answers(answers)
except StudentInputError as inst: except StudentInputError as inst:
self.lcp = LoncapaProblem(filename, id=lcp_id, state=old_state) self.lcp = LoncapaProblem(self.filestore.open(self.filename), id=lcp_id, state=old_state)
traceback.print_exc() traceback.print_exc()
# print {'error':sys.exc_info(),
# 'answers':answers,
# 'seed':self.lcp.seed,
# 'filename':self.lcp.filename}
return json.dumps({'success':inst.message}) return json.dumps({'success':inst.message})
except: except:
self.lcp = LoncapaProblem(filename, id=lcp_id, state=old_state) self.lcp = LoncapaProblem(self.filestore.open(self.filename), id=lcp_id, state=old_state)
traceback.print_exc() traceback.print_exc()
raise
return json.dumps({'success':'Unknown Error'}) return json.dumps({'success':'Unknown Error'})
...@@ -382,8 +369,8 @@ class Module(XModule): ...@@ -382,8 +369,8 @@ class Module(XModule):
self.lcp.questions=dict() # Detailed info about questions in problem instance. TODO: Should be by id and not lid. self.lcp.questions=dict() # Detailed info about questions in problem instance. TODO: Should be by id and not lid.
self.lcp.seed=None self.lcp.seed=None
filename=settings.DATA_DIR+"problems/"+self.filename+".xml" filename="problems/"+self.filename+".xml"
self.lcp=LoncapaProblem(filename, self.item_id, self.lcp.get_state()) self.lcp=LoncapaProblem(self.filestore.open(filename), self.item_id, self.lcp.get_state())
event_info['new_state']=self.lcp.get_state() event_info['new_state']=self.lcp.get_state()
self.tracker('reset_problem', event_info) self.tracker('reset_problem', event_info)
......
import json import json
## TODO: Abstract out from Django
from django.conf import settings
from mitxmako.shortcuts import render_to_response, render_to_string from mitxmako.shortcuts import render_to_response, render_to_string
from x_module import XModule from x_module import XModule
...@@ -24,13 +22,13 @@ class Module(XModule): ...@@ -24,13 +22,13 @@ class Module(XModule):
textlist=[i for i in textlist if type(i)==str] textlist=[i for i in textlist if type(i)==str]
return "".join(textlist) return "".join(textlist)
try: try:
filename=settings.DATA_DIR+"html/"+self.filename filename="html/"+self.filename
return open(filename).read() return self.filestore.open(filename).read()
except: # For backwards compatibility. TODO: Remove except: # For backwards compatibility. TODO: Remove
return render_to_string(self.filename, {'id': self.item_id}) return render_to_string(self.filename, {'id': self.item_id})
def __init__(self, xml, item_id, ajax_url=None, track_url=None, state=None, track_function=None, render_function = None): def __init__(self, system, xml, item_id, state=None):
XModule.__init__(self, xml, item_id, ajax_url, track_url, state, track_function, render_function) XModule.__init__(self, system, xml, item_id, state)
xmltree=etree.fromstring(xml) xmltree=etree.fromstring(xml)
self.filename = None self.filename = None
filename_l=xmltree.xpath("/html/@filename") filename_l=xmltree.xpath("/html/@filename")
......
...@@ -19,6 +19,6 @@ class Module(XModule): ...@@ -19,6 +19,6 @@ class Module(XModule):
def get_html(self): def get_html(self):
return '<input type="hidden" class="schematic" name="{item_id}" height="480" width="640">'.format(item_id=self.item_id) return '<input type="hidden" class="schematic" name="{item_id}" height="480" width="640">'.format(item_id=self.item_id)
def __init__(self, xml, item_id, ajax_url=None, track_url=None, state=None, render_function = None): def __init__(self, system, xml, item_id, state=None):
XModule.__init__(self, xml, item_id, ajax_url, track_url, state, render_function) XModule.__init__(self, system, xml, item_id, state)
...@@ -2,9 +2,7 @@ import json ...@@ -2,9 +2,7 @@ import json
from lxml import etree from lxml import etree
## TODO: Abstract out from Django from mitxmako.shortcuts import render_to_response, render_to_string
from django.http import Http404
from mitxmako.shortcuts import render_to_string
from x_module import XModule from x_module import XModule
...@@ -37,12 +35,10 @@ class Module(XModule): ...@@ -37,12 +35,10 @@ class Module(XModule):
return self.destroy_js return self.destroy_js
def handle_ajax(self, dispatch, get): def handle_ajax(self, dispatch, get):
print "GET", get
print "DISPATCH", dispatch
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 Http404() raise self.system.exception404
def render(self): def render(self):
if self.rendered: if self.rendered:
...@@ -106,9 +102,8 @@ class Module(XModule): ...@@ -106,9 +102,8 @@ class Module(XModule):
self.rendered = True self.rendered = True
def __init__(self, system, xml, item_id, state=None):
def __init__(self, xml, item_id, ajax_url=None, track_url=None, state=None, track_function=None, render_function = None): XModule.__init__(self, system, xml, item_id, state)
XModule.__init__(self, xml, item_id, ajax_url, track_url, state, track_function, render_function)
self.xmltree=etree.fromstring(xml) self.xmltree=etree.fromstring(xml)
self.position = 1 self.position = 1
......
...@@ -14,16 +14,16 @@ class Module(XModule): ...@@ -14,16 +14,16 @@ class Module(XModule):
@classmethod @classmethod
def get_xml_tags(c): def get_xml_tags(c):
## TODO: Abstract out from filesystem
tags = os.listdir(settings.DATA_DIR+'/custom_tags') tags = os.listdir(settings.DATA_DIR+'/custom_tags')
return tags return tags
def get_html(self): def get_html(self):
return self.html return self.html
def __init__(self, xml, item_id, ajax_url=None, track_url=None, state=None, track_function=None, render_function = None): def __init__(self, system, xml, item_id, state=None):
XModule.__init__(self, xml, item_id, ajax_url, track_url, state, track_function, render_function) XModule.__init__(self, system, xml, item_id, state)
xmltree = etree.fromstring(xml) xmltree = etree.fromstring(xml)
filename = xmltree.tag filename = xmltree.tag
params = dict(xmltree.items()) params = dict(xmltree.items())
# print params
self.html = render_to_string(filename, params, namespace = 'custom_tags') self.html = render_to_string(filename, params, namespace = 'custom_tags')
import json import json
## TODO: Abstract out from Django
from django.conf import settings
from mitxmako.shortcuts import render_to_response, render_to_string from mitxmako.shortcuts import render_to_response, render_to_string
from x_module import XModule from x_module import XModule
...@@ -26,8 +24,9 @@ class Module(XModule): ...@@ -26,8 +24,9 @@ class Module(XModule):
def get_destroy_js(self): def get_destroy_js(self):
return self.destroy_js_text return self.destroy_js_text
def __init__(self, xml, item_id, ajax_url=None, track_url=None, state=None, track_function=None, render_function = None):
XModule.__init__(self, xml, item_id, ajax_url, track_url, state, track_function, render_function) def __init__(self, system, xml, item_id, state=None):
XModule.__init__(self, system, xml, item_id, state)
xmltree=etree.fromstring(xml) xmltree=etree.fromstring(xml)
self.contents=[(e.get("name"),self.render_function(e)) \ self.contents=[(e.get("name"),self.render_function(e)) \
for e in xmltree] for e in xmltree]
......
...@@ -3,9 +3,7 @@ import logging ...@@ -3,9 +3,7 @@ import logging
from lxml import etree from lxml import etree
## TODO: Abstract out from Django from mitxmako.shortcuts import render_to_response, render_to_string
from django.http import Http404
from mitxmako.shortcuts import render_to_string
from x_module import XModule from x_module import XModule
...@@ -58,8 +56,8 @@ class Module(XModule): ...@@ -58,8 +56,8 @@ class Module(XModule):
def get_destroy_js(self): def get_destroy_js(self):
return "videoDestroy(\"{0}\");".format(self.item_id)+self.annotations_destroy return "videoDestroy(\"{0}\");".format(self.item_id)+self.annotations_destroy
def __init__(self, xml, item_id, ajax_url=None, track_url=None, state=None, track_function=None, render_function = None): def __init__(self, system, xml, item_id, state=None):
XModule.__init__(self, xml, item_id, ajax_url, track_url, state, track_function, render_function) XModule.__init__(self, system, xml, item_id, state)
xmltree=etree.fromstring(xml) xmltree=etree.fromstring(xml)
self.youtube = xmltree.get('youtube') self.youtube = xmltree.get('youtube')
self.name = xmltree.get('name') self.name = xmltree.get('name')
......
...@@ -45,13 +45,14 @@ class XModule(object): ...@@ -45,13 +45,14 @@ class XModule(object):
get is a dictionary-like object ''' get is a dictionary-like object '''
return "" return ""
def __init__(self, xml, item_id, ajax_url=None, track_url=None, state=None, track_function=None, render_function = None): def __init__(self, system, xml, item_id, track_url=None, state=None):
''' In most cases, you must pass state or xml''' ''' In most cases, you must pass state or xml'''
self.xml = xml self.xml = xml
self.item_id = item_id self.item_id = item_id
self.ajax_url = ajax_url
self.track_url = track_url
self.state = state self.state = state
self.tracker = track_function
self.render_function = render_function
self.ajax_url = system.ajax_url
self.tracker = system.track_function
self.filestore = system.filestore
self.render_function = system.render_function
self.system = system
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