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