Commit 7e177fc6 by Bridger Maxwell

Merge

--HG--
branch : bridgerwiki
parents f2f6f0c1 81e70347
...@@ -11,11 +11,12 @@ import calc, eia ...@@ -11,11 +11,12 @@ import calc, eia
from util import contextualize_text from util import contextualize_text
from inputtypes import textline, schematic from inputtypes import textline, schematic
from responsetypes import numericalresponse, formularesponse, customresponse from responsetypes import numericalresponse, formularesponse, customresponse, schematicresponse
response_types = {'numericalresponse':numericalresponse, response_types = {'numericalresponse':numericalresponse,
'formularesponse':formularesponse, 'formularesponse':formularesponse,
'customresponse':customresponse} 'customresponse':customresponse,
'schematicresponse':schematicresponse}
entry_types = ['textline', 'schematic'] entry_types = ['textline', 'schematic']
response_properties = ["responseparam", "answer"] response_properties = ["responseparam", "answer"]
# How to convert from original XML to HTML # How to convert from original XML to HTML
...@@ -23,6 +24,7 @@ response_properties = ["responseparam", "answer"] ...@@ -23,6 +24,7 @@ response_properties = ["responseparam", "answer"]
html_transforms = {'problem': {'tag':'div'}, html_transforms = {'problem': {'tag':'div'},
"numericalresponse": {'tag':'span'}, "numericalresponse": {'tag':'span'},
"customresponse": {'tag':'span'}, "customresponse": {'tag':'span'},
"schematicresponse": {'tag':'span'},
"formularesponse": {'tag':'span'}, "formularesponse": {'tag':'span'},
"text": {'tag':'span'}} "text": {'tag':'span'}}
...@@ -36,7 +38,7 @@ global_context={'random':random, ...@@ -36,7 +38,7 @@ global_context={'random':random,
# These should be removed from HTML output, including all subelements # These should be removed from HTML output, including all subelements
html_problem_semantics = ["responseparam", "answer", "script"] html_problem_semantics = ["responseparam", "answer", "script"]
# These should be removed from HTML output, but keeping subelements # These should be removed from HTML output, but keeping subelements
html_skip = ["numericalresponse", "customresponse", "formularesponse", "text"] html_skip = ["numericalresponse", "customresponse", "schematicresponse", "formularesponse", "text"]
# These should be transformed # These should be transformed
html_special_response = {"textline":textline.render, html_special_response = {"textline":textline.render,
"schematic":schematic.render} "schematic":schematic.render}
......
try:
from django.conf import settings
from auth.models import UserProfile
except:
settings = None
from xml.dom.minidom import parse, parseString
from lxml import etree
''' This file will eventually form an abstraction layer between the
course XML file and the rest of the system.
TODO: Shift everything from xml.dom.minidom to XPath (or XQuery)
'''
def xpath(xml, query_string, **args):
''' Safe xpath query into an xml tree:
* xml is the tree.
* query_string is the query
* args are the parameters. Substitute for {params}.
We should remove this with the move to lxml.
We should also use lxml argument passing. '''
doc = etree.fromstring(xml)
print type(doc)
def escape(x):
# TODO: This should escape the string. For now, we just assume it's made of valid characters.
# Couldn't figure out how to escape for lxml in a few quick Googles
valid_chars="".join(map(chr, range(ord('a'),ord('z')+1)+range(ord('A'),ord('Z')+1)+range(ord('0'), ord('9')+1)))+"_ "
for e in x:
if e not in valid_chars:
raise Exception("Invalid char in xpath expression. TODO: Escape")
return x
args=dict( ((k, escape(args[k])) for k in args) )
print args
results = doc.xpath(query_string.format(**args))
return results
def xpath_remove(tree, path):
''' Remove all items matching path from lxml tree. Works in
place.'''
items = tree.xpath(path)
for item in items:
item.getparent().remove(item)
return tree
if __name__=='__main__':
print xpath('<html><problem name="Bob"></problem></html>', '/{search}/problem[@name="{name}"]', search='html', name="Bob")
def item(l, default="", process=lambda x:x):
if len(l)==0:
return default
elif len(l)==1:
return process(l[0])
else:
raise Exception('Malformed XML')
def course_file(user):
# TODO: Cache. Also, return the libxml2 object.
return settings.DATA_DIR+UserProfile.objects.get(user=user).courseware
def module_xml(coursefile, module, id_tag, module_id):
''' Get XML for a module based on module and module_id. Assumes
module occurs once in courseware XML file.. '''
doc = etree.parse(coursefile)
# Sanitize input
if not module.isalnum():
raise Exception("Module is not alphanumeric")
if not module_id.isalnum():
raise Exception("Module ID is not alphanumeric")
xpath_search='//*/{module}[(@{id_tag} = "{id}") or (@id = "{id}")]'.format(module=module,
id_tag=id_tag,
id=module_id)
#result_set=doc.xpathEval(xpath_search)
result_set=doc.xpath(xpath_search)
if len(result_set)>1:
print "WARNING: Potentially malformed course file", module, module_id
if len(result_set)==0:
return None
return etree.tostring(result_set[0])
#return result_set[0].serialize()
def toc_from_xml(coursefile, active_chapter, active_section):
dom=parse(coursefile)
course = dom.getElementsByTagName('course')[0]
name=course.getAttribute("name")
chapters = course.getElementsByTagName('chapter')
ch=list()
for c in chapters:
if c.getAttribute("name") == 'hidden':
continue
sections=list()
for s in c.getElementsByTagName('section'):
sections.append({'name':s.getAttribute("name"),
'time':s.getAttribute("time"),
'format':s.getAttribute("format"),
'due':s.getAttribute("due"),
'active':(c.getAttribute("name")==active_chapter and \
s.getAttribute("name")==active_section)})
ch.append({'name':c.getAttribute("name"),
'sections':sections,
'active':(c.getAttribute("name")==active_chapter)})
return ch
def dom_select(dom, element_type, element_name):
if dom==None:
return None
elements=dom.getElementsByTagName(element_type)
for e in elements:
if e.getAttribute("name")==element_name:
return e
return None
...@@ -17,7 +17,19 @@ class schematic(object): ...@@ -17,7 +17,19 @@ class schematic(object):
eid = element.get('id') eid = element.get('id')
height = element.get('height') height = element.get('height')
width = element.get('width') width = element.get('width')
context = {'id':eid, 'value':value, 'state':state, 'width':width, 'height':height} parts = element.get('parts')
analyses = element.get('analyses')
initial_value = element.get('initial_value')
context = {
'id':eid,
'value':value,
'initial_value':initial_value,
'state':state,
'width':width,
'height':height,
'parts':parts,
'analyses':analyses,
}
html=render_to_string("schematicinput.html", context) html=render_to_string("schematicinput.html", context)
return etree.XML(html) return etree.XML(html)
......
import random, numpy, math, scipy import random, numpy, math, scipy, json
from util import contextualize_text from util import contextualize_text
from calc import evaluator from calc import evaluator
import random, math import random, math
...@@ -63,7 +63,6 @@ class customresponse(object): ...@@ -63,7 +63,6 @@ class customresponse(object):
# be handled by capa_problem # be handled by capa_problem
return {} return {}
class formularesponse(object): class formularesponse(object):
def __init__(self, xml, context): def __init__(self, xml, context):
self.xml = xml self.xml = xml
...@@ -114,3 +113,28 @@ class formularesponse(object): ...@@ -114,3 +113,28 @@ class formularesponse(object):
def get_answers(self): def get_answers(self):
return {self.answer_id:self.correct_answer} return {self.answer_id:self.correct_answer}
class schematicresponse(object):
def __init__(self, xml, context):
self.xml = xml
self.answer_ids = xml.xpath('//*[@id=$id]//schematic/@id',
id=xml.get('id'))
self.context = context
answer = xml.xpath('//*[@id=$id]//answer',
id=xml.get('id'))[0]
answer_src = answer.get('src')
if answer_src != None:
self.code = open(settings.DATA_DIR+'src/'+answer_src).read()
else:
self.code = answer.text
def grade(self, student_answers):
submission = [json.loads(student_answers[k]) for k in sorted(self.answer_ids)]
self.context.update({'submission':submission})
exec self.code in global_context, self.context
return zip(sorted(self.answer_ids), self.context['correct'])
def get_answers(self):
# Since this is explicitly specified in the problem, this will
# be handled by capa_problem
return {}
from django.conf import settings try:
from xml.dom.minidom import parse, parseString from django.conf import settings
from auth.models import UserProfile
except:
settings = None
from lxml import etree from lxml import etree
from auth.models import UserProfile import json
import hashlib
''' This file will eventually form an abstraction layer between the ''' This file will eventually form an abstraction layer between the
course XML file and the rest of the system. course XML file and the rest of the system.
...@@ -11,11 +15,18 @@ course XML file and the rest of the system. ...@@ -11,11 +15,18 @@ course XML file and the rest of the system.
TODO: Shift everything from xml.dom.minidom to XPath (or XQuery) TODO: Shift everything from xml.dom.minidom to XPath (or XQuery)
''' '''
def fasthash(string):
m = hashlib.new("md4")
m.update(string)
return "id"+m.hexdigest()
def xpath(xml, query_string, **args): def xpath(xml, query_string, **args):
''' Safe xpath query into an xml tree: ''' Safe xpath query into an xml tree:
* xml is the tree. * xml is the tree.
* query_string is the query * query_string is the query
* args are the parameters. Substitute for {params}. ''' * args are the parameters. Substitute for {params}.
We should remove this with the move to lxml.
We should also use lxml argument passing. '''
doc = etree.fromstring(xml) doc = etree.fromstring(xml)
print type(doc) print type(doc)
def escape(x): def escape(x):
...@@ -32,8 +43,17 @@ def xpath(xml, query_string, **args): ...@@ -32,8 +43,17 @@ def xpath(xml, query_string, **args):
results = doc.xpath(query_string.format(**args)) results = doc.xpath(query_string.format(**args))
return results return results
def xpath_remove(tree, path):
''' Remove all items matching path from lxml tree. Works in
place.'''
items = tree.xpath(path)
for item in items:
item.getparent().remove(item)
return tree
if __name__=='__main__': if __name__=='__main__':
print xpath('<html><problem name="Bob"></problem></html>', '/{search}/problem[@name="{name}"]', search='html', name="Bob") print xpath('<html><problem name="Bob"></problem></html>', '/{search}/problem[@name="{name}"]',
search='html', name="Bob")
def item(l, default="", process=lambda x:x): def item(l, default="", process=lambda x:x):
if len(l)==0: if len(l)==0:
...@@ -43,15 +63,38 @@ def item(l, default="", process=lambda x:x): ...@@ -43,15 +63,38 @@ def item(l, default="", process=lambda x:x):
else: else:
raise Exception('Malformed XML') raise Exception('Malformed XML')
def id_tag(course):
''' Tag all course elements with unique IDs '''
default_ids = {'video':'youtube',
'problem':'filename',
'sequential':'id',
'html':'filename',
'vertical':'id',
'tab':'id',
'schematic':'id'}
# Tag elements with unique IDs
elements = course.xpath("|".join(['//'+c for c in default_ids]))
for elem in elements:
if elem.get('id'):
pass
elif elem.get(default_ids[elem.tag]):
new_id = elem.get(default_ids[elem.tag]) # Convert to alphanumeric
new_id = "".join([a for a in new_id if a.isalnum()])
elem.set('id', new_id)
else:
elem.set('id', fasthash(etree.tostring(elem)))
def course_file(user): def course_file(user):
# TODO: Cache. Also, return the libxml2 object. # TODO: Cache.
return settings.DATA_DIR+UserProfile.objects.get(user=user).courseware tree = etree.parse(settings.DATA_DIR+UserProfile.objects.get(user=user).courseware)
id_tag(tree)
return tree
def module_xml(coursefile, module, id_tag, module_id): def module_xml(coursefile, module, id_tag, module_id):
''' Get XML for a module based on module and module_id. Assumes ''' Get XML for a module based on module and module_id. Assumes
module occurs once in courseware XML file.. ''' module occurs once in courseware XML file.. '''
doc = etree.parse(coursefile) doc = coursefile
# Sanitize input # Sanitize input
if not module.isalnum(): if not module.isalnum():
...@@ -70,35 +113,24 @@ def module_xml(coursefile, module, id_tag, module_id): ...@@ -70,35 +113,24 @@ def module_xml(coursefile, module, id_tag, module_id):
return etree.tostring(result_set[0]) return etree.tostring(result_set[0])
#return result_set[0].serialize() #return result_set[0].serialize()
def toc_from_xml(coursefile, active_chapter, active_section): def toc_from_xml(dom, active_chapter, active_section):
dom=parse(coursefile) name = dom.xpath('//course/@name')[0]
course = dom.getElementsByTagName('course')[0] chapters = dom.xpath('//course[@name=$name]/chapter', name=name)
name=course.getAttribute("name")
chapters = course.getElementsByTagName('chapter')
ch=list() ch=list()
for c in chapters: for c in chapters:
if c.getAttribute("name") == 'hidden': if c.get('name') == 'hidden':
continue continue
sections=list() sections=list()
for s in c.getElementsByTagName('section'): for s in dom.xpath('//course[@name=$name]/chapter[@name=$chname]/section', name=name, chname=c.get('name')):
sections.append({'name':s.getAttribute("name"), sections.append({'name':s.get("name") or "",
'time':s.getAttribute("time"), 'time':s.get("time") or "",
'format':s.getAttribute("format"), 'format':s.get("format") or "",
'due':s.getAttribute("due"), 'due':s.get("due") or "",
'active':(c.getAttribute("name")==active_chapter and \ 'active':(c.get("name")==active_chapter and \
s.getAttribute("name")==active_section)}) s.get("name")==active_section)})
ch.append({'name':c.getAttribute("name"), ch.append({'name':c.get("name"),
'sections':sections, 'sections':sections,
'active':(c.getAttribute("name")==active_chapter)}) 'active':(c.get("name")==active_chapter)})
return ch return ch
def dom_select(dom, element_type, element_name):
if dom==None:
return None
elements=dom.getElementsByTagName(element_type)
for e in elements:
if e.getAttribute("name")==element_name:
return e
return None
...@@ -16,10 +16,12 @@ from django.http import Http404 ...@@ -16,10 +16,12 @@ from django.http import Http404
import urllib import urllib
import capa_module import courseware.modules.capa_module
import video_module import courseware.modules.video_module
import html_module import courseware.modules.vertical_module
import schematic_module import courseware.modules.html_module
import courseware.modules.schematic_module
import courseware.modules.seq_module
from models import StudentModule from models import StudentModule
...@@ -29,12 +31,18 @@ from django.conf import settings ...@@ -29,12 +31,18 @@ from django.conf import settings
import content_parser import content_parser
import sys
from lxml import etree
import uuid import uuid
modx_modules={'problem':capa_module.LoncapaModule, ## TODO: Add registration mechanism
'video':video_module.VideoModule, modx_modules={'problem':courseware.modules.capa_module.LoncapaModule,
'html':html_module.HtmlModule, 'video':courseware.modules.video_module.VideoModule,
'schematic':schematic_module.SchematicModule} 'html':courseware.modules.html_module.HtmlModule,
'vertical':courseware.modules.vertical_module.VerticalModule,
'sequential':courseware.modules.seq_module.SequentialModule,
'schematic':courseware.modules.schematic_module.SchematicModule}
def make_track_function(request): def make_track_function(request):
def f(event_type, event): def f(event_type, event):
...@@ -44,11 +52,12 @@ def make_track_function(request): ...@@ -44,11 +52,12 @@ def make_track_function(request):
def modx_dispatch(request, module=None, dispatch=None, id=None): def modx_dispatch(request, module=None, dispatch=None, id=None):
''' Generic view for extensions. ''' ''' Generic view for extensions. '''
# Grab the student information for the module from the database # Grab the student information for the module from the database
print module, request.user, id
s = StudentModule.objects.filter(module_type=module, s = StudentModule.objects.filter(module_type=module,
student=request.user, student=request.user,
module_id=id) module_id=id)
if len(s) == 0: if len(s) == 0:
print "ls404" print "ls404", module, request.user, id
raise Http404 raise Http404
s=s[0] s=s[0]
...@@ -67,83 +76,25 @@ def modx_dispatch(request, module=None, dispatch=None, id=None): ...@@ -67,83 +76,25 @@ def modx_dispatch(request, module=None, dispatch=None, id=None):
s.module_id, s.module_id,
ajax_url=ajax_url, ajax_url=ajax_url,
state=s.state, state=s.state,
track_function = make_track_function(request)) track_function = make_track_function(request),
render_function = render_module,
meta = request)
# 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
s.state=instance.get_state() s.state=instance.get_state()
if instance.get_score() != None:
s.grade=instance.get_score()['score'] s.grade=instance.get_score()['score']
s.save() s.save()
# Return whatever the module wanted to return to the client/caller # Return whatever the module wanted to return to the client/caller
return HttpResponse(ajax_return) return HttpResponse(ajax_return)
def vertical_module(request, module):
''' Layout module which lays out content vertically.
'''
contents=[(e.getAttribute("name"),render_module(request, e)) \
for e in module.childNodes \
if e.nodeType==1]
init_js="".join([e[1]['init_js'] for e in contents if 'init_js' in e[1]])
destroy_js="".join([e[1]['destroy_js'] for e in contents if 'destroy_js' in e[1]])
return {'init_js':init_js,
'destroy_js':destroy_js,
'content':render_to_string('vert_module.html',{'items':contents}),
'type':'vertical'}
def seq_module(request, module):
''' Layout module which lays out content in a temporal sequence
'''
def j(m):
# jsonify contents so it can be embedded in a js array
# We also need to split </script> tags so they don't break
# mid-string
if 'init_js' not in m: m['init_js']=""
if 'type' not in m: m['init_js']=""
content=json.dumps(m['content'])
content=content.replace('</script>', '<"+"/script>')
return {'content':content,
"destroy_js":m['destroy_js'],
'init_js':m['init_js'],
'type':m['type']}
contents=[(e.getAttribute("name"),j(render_module(request, e))) \
for e in module.childNodes \
if e.nodeType==1]
js=""
iid=uuid.uuid1().hex
params={'items':contents,
'id':"seq"}
# TODO/BUG: Destroy JavaScript should only be called for the active view
# This calls it for all the views
#
# To fix this, we'd probably want to have some way of assigning unique
# IDs to sequences.
destroy_js="".join([e[1]['destroy_js'] for e in contents if 'destroy_js' in e[1]])
if module.nodeName == 'sequential':
return {'init_js':js+render_to_string('seq_module.js',params),
"destroy_js":destroy_js,
'content':render_to_string('seq_module.html',params),
'type':'sequential'}
if module.nodeName == 'tab':
params['id'] = 'tab'
return {'init_js':js+render_to_string('tab_module.js',params),
"destroy_js":destroy_js,
'content':render_to_string('tab_module.html',params),
'type':'tab'}
def render_x_module(request, xml_module): def render_x_module(request, xml_module):
''' Generic module for extensions. This renders to HTML. ''' ''' Generic module for extensions. This renders to HTML. '''
# Check if problem has an instance in DB # Check if problem has an instance in DB
module_type=xml_module.nodeName module_type=xml_module.tag
module_class=modx_modules[module_type] module_class=modx_modules[module_type]
module_id=xml_module.getAttribute(module_class.id_attribute) module_id=xml_module.get('id') #module_class.id_attribute) or ""
# Grab state from database # Grab state from database
s = StudentModule.objects.filter(student=request.user, s = StudentModule.objects.filter(student=request.user,
...@@ -157,11 +108,13 @@ def render_x_module(request, xml_module): ...@@ -157,11 +108,13 @@ def render_x_module(request, xml_module):
# 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(xml_module.toxml(), instance=module_class(etree.tostring(xml_module),
module_id, module_id,
ajax_url=ajax_url, ajax_url=ajax_url,
state=state, state=state,
track_function = make_track_function(request)) track_function = make_track_function(request),
render_function = render_module,
meta = request)
# If instance wasn't already in the database, create it # If instance wasn't already in the database, create it
if len(s) == 0: if len(s) == 0:
...@@ -179,20 +132,8 @@ def render_x_module(request, xml_module): ...@@ -179,20 +132,8 @@ def render_x_module(request, xml_module):
return content return content
module_types={'video':render_x_module,
'html':render_x_module,
'tab':seq_module,
'vertical':vertical_module,
'sequential':seq_module,
'problem':render_x_module,
'schematic':render_x_module
}
def render_module(request, module): def render_module(request, module):
''' Generic dispatch for internal modules. ''' ''' Generic dispatch for internal modules. '''
if module==None: if module==None :
return {"content":""} return {"content":""}
if str(module.localName) in module_types: return render_x_module(request, module)
return module_types[module.localName](request, module)
print "rm404"
raise Http404
...@@ -2,14 +2,14 @@ import random, numpy, math, scipy, sys, StringIO, os, struct, json ...@@ -2,14 +2,14 @@ import random, numpy, math, scipy, sys, StringIO, os, struct, json
from x_module import XModule from x_module import XModule
import sys import sys
from capa.capa_problem import LoncapaProblem from courseware.capa.capa_problem import LoncapaProblem
from django.http import Http404 from django.http import Http404
import dateutil import dateutil
import dateutil.parser import dateutil.parser
import datetime import datetime
import content_parser import courseware.content_parser as content_parser
from lxml import etree from lxml import etree
...@@ -108,8 +108,8 @@ class LoncapaModule(XModule): ...@@ -108,8 +108,8 @@ class LoncapaModule(XModule):
return html return html
def __init__(self, xml, item_id, ajax_url=None, track_url=None, state=None, track_function=None): 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) XModule.__init__(self, xml, item_id, ajax_url, track_url, state, track_function, render_function)
self.attempts = 0 self.attempts = 0
self.max_attempts = None self.max_attempts = None
......
...@@ -25,8 +25,8 @@ class HtmlModule(XModule): ...@@ -25,8 +25,8 @@ class HtmlModule(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)
def __init__(self, xml, item_id, ajax_url=None, track_url=None, state=None, track_function=None): 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) XModule.__init__(self, xml, item_id, ajax_url, track_url, state, track_function, render_function)
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")
......
...@@ -18,6 +18,6 @@ class SchematicModule(XModule): ...@@ -18,6 +18,6 @@ class SchematicModule(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): def __init__(self, xml, item_id, ajax_url=None, track_url=None, state=None, render_function = None, meta = None):
XModule.__init__(self, xml, item_id, ajax_url, track_url, state) XModule.__init__(self, xml, item_id, ajax_url, track_url, state, render_function)
from x_module import XModule
from lxml import etree
from django.http import Http404
import json
## TODO: Abstract out from Django
from django.conf import settings
from djangomako.shortcuts import render_to_response, render_to_string
class SequentialModule(XModule):
''' Layout module which lays out content in a temporal sequence
'''
id_attribute = 'id'
def get_state(self):
return json.dumps({ 'position':self.position })
def get_xml_tags():
return ["sequential", 'tab']
def get_html(self):
return self.content
def get_init_js(self):
return self.init_js
def get_destroy_js(self):
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()
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)
xmltree=etree.fromstring(xml)
self.position = 1
if state!=None:
state = json.loads(state)
if 'position' in state: self.position = int(state['position'])
def j(m):
''' jsonify contents so it can be embedded in a js array
We also need to split </script> tags so they don't break
mid-string'''
if 'init_js' not in m: m['init_js']=""
if 'type' not in m: m['init_js']=""
content=json.dumps(m['content'])
content=content.replace('</script>', '<"+"/script>')
return {'content':content,
"destroy_js":m['destroy_js'],
'init_js':m['init_js'],
'type':m['type']}
contents=[(e.get("name"),j(render_function(meta, e))) \
for e in xmltree]
js=""
params={'items':contents,
'id':item_id,
'position': self.position}
# TODO/BUG: Destroy JavaScript should only be called for the active view
# This calls it for all the views
#
# To fix this, we'd probably want to have some way of assigning unique
# IDs to sequences.
destroy_js="".join([e[1]['destroy_js'] for e in contents if 'destroy_js' in e[1]])
if xmltree.tag == 'sequential':
self.init_js=js+render_to_string('seq_module.js',params)
self.destroy_js=destroy_js
self.content=render_to_string('seq_module.html',params)
if xmltree.tag == 'tab':
params['id'] = 'tab'
self.init_js=js+render_to_string('tab_module.js',params)
self.destroy_js=destroy_js
self.content=render_to_string('tab_module.html',params)
from x_module import XModule
from lxml import etree
import json
## TODO: Abstract out from Django
from django.conf import settings
from djangomako.shortcuts import render_to_response, render_to_string
class VerticalModule(XModule):
id_attribute = 'id'
def get_state(self):
return json.dumps({ })
def get_xml_tags():
return "vertical"
def get_html(self):
return render_to_string('vert_module.html',{'items':self.contents})
def get_init_js(self):
return self.init_js_text
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, meta = None):
XModule.__init__(self, xml, item_id, ajax_url, track_url, state, track_function, render_function)
xmltree=etree.fromstring(xml)
self.contents=[(e.get("name"),self.render_function(meta, e)) \
for e in xmltree]
self.init_js_text="".join([e[1]['init_js'] for e in self.contents if 'init_js' in e[1]])
self.destroy_js_text="".join([e[1]['destroy_js'] for e in self.contents if 'destroy_js' in e[1]])
from x_module import XModule from x_module import XModule
from lxml import etree
import json import json
...@@ -7,47 +8,55 @@ from django.conf import settings ...@@ -7,47 +8,55 @@ from django.conf import settings
from djangomako.shortcuts import render_to_response, render_to_string from djangomako.shortcuts import render_to_response, render_to_string
class VideoModule(XModule): class VideoModule(XModule):
id_attribute = 'youtube' #id_attribute = 'youtube'
video_time = 0 video_time = 0
def handle_ajax(self, dispatch, get): def handle_ajax(self, dispatch, get):
if dispatch == 'time': print "GET", get
self.video_time = int(get['time']) print "DISPATCH", dispatch
print self.video_time if dispatch=='goto_position':
self.position = int(float(get['position']))
return json.dumps("True") print "NEW POSITION", self.position
return json.dumps({'success':True})
raise Http404()
def get_state(self): def get_state(self):
return json.dumps({ 'time':self.video_time }) print "STATE POSITION", self.position
return json.dumps({ 'position':self.position })
def get_xml_tags(): def get_xml_tags():
''' Tags in the courseware file guaranteed to correspond to the module ''' ''' Tags in the courseware file guaranteed to correspond to the module '''
return "video" return "video"
def video_list(self): def video_list(self):
l=self.item_id.split(',') l=self.youtube.split(',')
l=[i.split(":") for i in l] l=[i.split(":") for i in l]
return json.dumps(dict(l)) return json.dumps(dict(l))
def get_html(self): def get_html(self):
return render_to_string('video.html',{'streams':self.video_list(), return render_to_string('video.html',{'streams':self.video_list(),
'id':self.item_id, 'id':self.item_id,
'video_time':self.video_time}) 'position':self.position})
def get_init_js(self): def get_init_js(self):
''' JavaScript code to be run when problem is shown. Be aware ''' JavaScript code to be run when problem is shown. Be aware
that this may happen several times on the same page that this may happen several times on the same page
(e.g. student switching tabs). Common functions should be put (e.g. student switching tabs). Common functions should be put
in the main course .js files for now. ''' in the main course .js files for now. '''
print "INIT POSITION", self.position
return render_to_string('video_init.js',{'streams':self.video_list(), return render_to_string('video_init.js',{'streams':self.video_list(),
'id':self.item_id, 'id':self.item_id,
'video_time':self.video_time}) 'position':self.position})
def get_destroy_js(self): def get_destroy_js(self):
return "videoDestroy();" return "videoDestroy(\""+self.item_id+"\");"
def __init__(self, xml, item_id, ajax_url=None, track_url=None, state=None, track_function=None): 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) XModule.__init__(self, xml, item_id, ajax_url, track_url, state, track_function, render_function)
print state self.youtube = etree.XML(xml).get('youtube')
if state!=None and "time" not in json.loads(state): self.position = 0
self.video_time = 0 if state!=None:
state = json.loads(state)
if 'position' in state: self.position = int(float(state['position']))
print "POOSITION IN STATE"
print "LOAD POSITION", self.position
...@@ -39,11 +39,13 @@ class XModule(object): ...@@ -39,11 +39,13 @@ 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): def __init__(self, xml, item_id, ajax_url=None, track_url=None, state=None, track_function=None, render_function = None, meta = 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.ajax_url = ajax_url
self.track_url=track_url self.track_url = track_url
self.state=state self.state = state
self.tracker=track_function self.tracker = track_function
self.render_function = render_function
self.meta = meta
from django.http import HttpResponse from django.http import HttpResponse
from django.template import Context, loader from django.template import Context, loader
from djangomako.shortcuts import render_to_response, render_to_string from djangomako.shortcuts import render_to_response, render_to_string
from xml.dom.minidom import parse, parseString
import json, os, sys import json, os, sys
from django.core.context_processors import csrf from django.core.context_processors import csrf
...@@ -14,11 +13,6 @@ import StringIO ...@@ -14,11 +13,6 @@ import StringIO
from django.http import Http404 from django.http import Http404
import urllib
import capa_module
import video_module
from models import StudentModule from models import StudentModule
import urllib import urllib
...@@ -31,6 +25,11 @@ import uuid ...@@ -31,6 +25,11 @@ import uuid
from module_render import * from module_render import *
from lxml import etree
etree.set_default_parser(etree.XMLParser(dtd_validation=False, load_dtd=False,
remove_comments = True))
template_imports={'urllib':urllib} template_imports={'urllib':urllib}
def profile(request): def profile(request):
...@@ -39,20 +38,23 @@ def profile(request): ...@@ -39,20 +38,23 @@ def profile(request):
if not request.user.is_authenticated(): if not request.user.is_authenticated():
return redirect('/') return redirect('/')
dom=parse(content_parser.course_file(request.user)) dom=content_parser.course_file(request.user)
hw=[] hw=[]
course = dom.getElementsByTagName('course')[0] course = dom.xpath('//course/@name')[0]
chapters = course.getElementsByTagName('chapter') chapters = dom.xpath('//course[@name=$course]/chapter', course=course)
responses=StudentModule.objects.filter(student=request.user) responses=StudentModule.objects.filter(student=request.user)
for c in chapters: for c in chapters:
for s in c.getElementsByTagName('section'): chname=c.get('name')
problems=s.getElementsByTagName('problem') for s in dom.xpath('//course[@name=$course]/chapter[@name=$chname]/section',
course=course, chname=chname):
problems=dom.xpath('//course[@name=$course]/chapter[@name=$chname]/section[@name=$section]//problem',
course=course, chname=chname, section=s.get('name'))
scores=[] scores=[]
if len(problems)>0: if len(problems)>0:
for p in problems: for p in problems:
id = p.getAttribute('filename') id = p.get('filename')
correct = 0 correct = 0
for response in responses: for response in responses:
if response.module_id == id: if response.module_id == id:
...@@ -60,11 +62,11 @@ def profile(request): ...@@ -60,11 +62,11 @@ def profile(request):
correct=response.grade correct=response.grade
else: else:
correct=0 correct=0
total=capa_module.LoncapaModule(p.toxml(), "id").max_score() # TODO: Add state. Not useful now, but maybe someday problems will have randomized max scores? total=courseware.modules.capa_module.LoncapaModule(etree.tostring(p), "id").max_score() # TODO: Add state. Not useful now, but maybe someday problems will have randomized max scores?
scores.append((int(correct),total)) scores.append((int(correct),total))
score={'course':course.getAttribute('name'), score={'course':course,
'section':s.getAttribute("name"), 'section':s.get("name"),
'chapter':c.getAttribute("name"), 'chapter':c.get("name"),
'scores':scores, 'scores':scores,
} }
hw.append(score) hw.append(score)
...@@ -109,6 +111,7 @@ def index(request, course="6.002 Spring 2012", chapter="Using the System", secti ...@@ -109,6 +111,7 @@ def index(request, course="6.002 Spring 2012", chapter="Using the System", secti
# Fixes URLs -- we don't get funny encoding characters from spaces # Fixes URLs -- we don't get funny encoding characters from spaces
# so they remain readable # so they remain readable
## TODO: Properly replace underscores
course=course.replace("_"," ") course=course.replace("_"," ")
chapter=chapter.replace("_"," ") chapter=chapter.replace("_"," ")
section=section.replace("_"," ") section=section.replace("_"," ")
...@@ -118,15 +121,13 @@ def index(request, course="6.002 Spring 2012", chapter="Using the System", secti ...@@ -118,15 +121,13 @@ def index(request, course="6.002 Spring 2012", chapter="Using the System", secti
if course!="6.002 Spring 2012": if course!="6.002 Spring 2012":
return redirect('/') return redirect('/')
cf = content_parser.course_file(request.user) dom = content_parser.course_file(request.user)
dom=parse(cf) dom_module = dom.xpath("//course[@name=$course]/chapter[@name=$chapter]//section[@name=$section]/*[1]",
dom_course=content_parser.dom_select(dom, 'course', course) course=course, chapter=chapter, section=section)
dom_chapter=content_parser.dom_select(dom_course, 'chapter', chapter) if len(dom_module) == 0:
dom_section=content_parser.dom_select(dom_chapter, 'section', section) module = None
if dom_section!=None:
module=[e for e in dom_section.childNodes if e.nodeType==1][0]
else: else:
module=None module = dom_module[0]
accordion=render_accordion(request, course, chapter, section) accordion=render_accordion(request, course, chapter, section)
...@@ -135,6 +136,8 @@ def index(request, course="6.002 Spring 2012", chapter="Using the System", secti ...@@ -135,6 +136,8 @@ def index(request, course="6.002 Spring 2012", chapter="Using the System", secti
if 'init_js' not in module: if 'init_js' not in module:
module['init_js']='' module['init_js']=''
context={'init':accordion['init_js']+module['init_js'], context={'init':accordion['init_js']+module['init_js'],
'accordion':accordion['content'], 'accordion':accordion['content'],
'content':module['content'], 'content':module['content'],
......
settings_old_askbot.py
\ No newline at end of file
import os
import sys
import djcelery
COURSEWARE_ENABLED = True
ASKBOT_ENABLED = True
CSRF_COOKIE_DOMAIN = '127.0.0.1'
# Defaults to be overridden
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
SITE_NAME = "localhost:8000"
DEFAULT_FROM_EMAIL = 'registration@mitx.mit.edu'
DEFAULT_FEEDBACK_EMAIL = 'feedback@mitx.mit.edu'
GENERATE_RANDOM_USER_CREDENTIALS = False
WIKI_REQUIRE_LOGIN_EDIT = True
WIKI_REQUIRE_LOGIN_VIEW = True
PERFSTATS = False
HTTPS = 'on'
MEDIA_URL = ''
MEDIA_ROOT = ''
DEBUG = True
TEMPLATE_DEBUG = DEBUG
ADMINS = (
('Piotr Mitros', 'pmitros@csail.mit.edu'),
)
MANAGERS = ADMINS
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
TIME_ZONE = 'America/Chicago'
# Language code for this installation. All choices can be found here:
# http://www.i18nguy.com/unicode/language-identifiers.html
LANGUAGE_CODE = 'en'
SITE_ID = 1
# If you set this to False, Django will make some optimizations so as not
# to load the internationalization machinery.
USE_I18N = True
# If you set this to False, Django will not format dates, numbers and
# calendars according to the current locale
USE_L10N = True
STATIC_URL = '/static/'
# URL prefix for admin static files -- CSS, JavaScript and images.
# Make sure to use a trailing slash.
# Examples: "http://foo.com/static/admin/", "/static/admin/".
ADMIN_MEDIA_PREFIX = '/static/admin/'
# List of finder classes that know how to find static files in
# various locations.
STATICFILES_FINDERS = (
'django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
# 'django.contrib.staticfiles.finders.DefaultStorageFinder',
)
# List of callables that know how to import templates from various sources.
TEMPLATE_LOADERS = (
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
# 'django.template.loaders.eggs.Loader',
)
MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'track.middleware.TrackMiddleware',
'djangomako.middleware.MakoMiddleware',
#'debug_toolbar.middleware.DebugToolbarMiddleware',
)
ROOT_URLCONF = 'mitx.urls'
INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.messages',
'django.contrib.staticfiles',
'courseware',
'auth',
'django.contrib.humanize',
'static_template_view',
'staticbook',
'simplewiki',
'track',
'circuit',
'perfstats',
# Uncomment the next line to enable the admin:
# 'django.contrib.admin',
# Uncomment the next line to enable admin documentation:
# 'django.contrib.admindocs',
)
# A sample logging configuration. The only tangible logging
# performed by this configuration is to send an email to
# the site admins on every HTTP 500 error.
# See http://docs.djangoproject.com/en/dev/topics/logging for
# more details on how to customize your logging configuration.
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'mail_admins': {
'level': 'ERROR',
'class': 'django.utils.log.AdminEmailHandler'
}
},
'loggers': {
'django.request': {
'handlers': ['mail_admins'],
'level': 'ERROR',
'propagate': True,
},
}
}
#TRACK_DIR = None
DEBUG_TRACK_LOG = False
# Maximum length of a tracking string. We don't want e.g. a file upload in our log
TRACK_MAX_EVENT = 1000
# Maximum length of log file before starting a new one.
MAXLOG = 500
# Our parent dir (mitx_all) is the BASE_DIR
BASE_DIR = os.path.abspath(os.path.join(__file__, "..", ".."))
# Make sure we execute correctly regardless of where we're called from
execfile(os.path.join(BASE_DIR, "settings.py"))
if PERFSTATS :
MIDDLEWARE_CLASSES = ( 'perfstats.middleware.ProfileMiddleware',) + MIDDLEWARE_CLASSES
if 'TRACK_DIR' not in locals():
TRACK_DIR = BASE_DIR+'/track_dir/'
if 'STATIC_ROOT' not in locals():
STATIC_ROOT = BASE_DIR+'/staticroot/'
if 'DATA_DIR' not in locals():
DATA_DIR = BASE_DIR+'/data/'
if 'TEXTBOOK_DIR' not in locals():
TEXTBOOK_DIR = BASE_DIR+'/textbook/'
if 'TEMPLATE_DIRS' not in locals():
TEMPLATE_DIRS = (
BASE_DIR+'/templates/',
DATA_DIR+'/templates',
TEXTBOOK_DIR,
)
if 'STATICFILES_DIRS' not in locals():
STATICFILES_DIRS = (
BASE_DIR+'/3rdParty/static',
BASE_DIR+'/static',
)
if 'ASKBOT_EXTRA_SKINS_DIR' not in locals():
ASKBOT_EXTRA_SKINS_DIR = BASE_DIR+'/askbot-devel/askbot/skins'
if 'ASKBOT_DIR' not in locals():
ASKBOT_DIR = BASE_DIR+'/askbot-devel'
sys.path.append(ASKBOT_DIR)
import askbot
import site
STATICFILES_DIRS = STATICFILES_DIRS + ( ASKBOT_DIR+'/askbot/skins',)
# Needed for Askbot
# Critical TODO: Move to S3
MEDIA_URL = '/discussion/upfiles/'
MEDIA_ROOT = ASKBOT_DIR+'/askbot/upfiles'
ASKBOT_ROOT = os.path.dirname(askbot.__file__)
site.addsitedir(os.path.join(os.path.dirname(askbot.__file__), 'deps'))
TEMPLATE_LOADERS = TEMPLATE_LOADERS + ('askbot.skins.loaders.filesystem_load_template_source',)
MIDDLEWARE_CLASSES = MIDDLEWARE_CLASSES + (
'askbot.middleware.anon_user.ConnectToSessionMessagesMiddleware',
'askbot.middleware.forum_mode.ForumModeMiddleware',
'askbot.middleware.cancel.CancelActionMiddleware',
'django.middleware.transaction.TransactionMiddleware',
#'debug_toolbar.middleware.DebugToolbarMiddleware',
'askbot.middleware.view_log.ViewLogMiddleware',
'askbot.middleware.spaceless.SpacelessMiddleware',
# 'askbot.middleware.pagesize.QuestionsPageSizeMiddleware',
)
FILE_UPLOAD_TEMP_DIR = os.path.join(os.path.dirname(__file__), 'tmp').replace('\\','/')
FILE_UPLOAD_HANDLERS = (
'django.core.files.uploadhandler.MemoryFileUploadHandler',
'django.core.files.uploadhandler.TemporaryFileUploadHandler',
)
ASKBOT_ALLOWED_UPLOAD_FILE_TYPES = ('.jpg', '.jpeg', '.gif', '.bmp', '.png', '.tiff')
ASKBOT_MAX_UPLOAD_FILE_SIZE = 1024 * 1024 #result in bytes
# ASKBOT_FILE_UPLOAD_DIR = os.path.join(os.path.dirname(__file__), 'askbot', 'upfiles')
DEFAULT_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage'
PROJECT_ROOT = os.path.dirname(__file__)
TEMPLATE_CONTEXT_PROCESSORS = (
'django.core.context_processors.request',
'askbot.context.application_settings',
#'django.core.context_processors.i18n',
'askbot.user_messages.context_processors.user_messages',#must be before auth
'django.core.context_processors.auth', #this is required for admin
'django.core.context_processors.csrf', #necessary for csrf protection
)
INSTALLED_APPS = INSTALLED_APPS + (
'django.contrib.sitemaps',
'django.contrib.admin',
'south',
'askbot.deps.livesettings',
'askbot',
#'keyedcache', # TODO: Main askbot tree has this installed, but we get intermittent errors if we include it.
'robots',
'django_countries',
'djcelery',
'djkombu',
'followit',
)
CACHE_MIDDLEWARE_ANONYMOUS_ONLY = True
ASKBOT_URL = 'discussion/'
LOGIN_REDIRECT_URL = '/'
LOGIN_URL = '/'
# ASKBOT_UPLOADED_FILES_URL = '%s%s' % (ASKBOT_URL, 'upfiles/')
ALLOW_UNICODE_SLUGS = False
ASKBOT_USE_STACKEXCHANGE_URLS = False #mimic url scheme of stackexchange
ASKBOT_CSS_DEVEL = True
# Celery Settings
BROKER_TRANSPORT = "djkombu.transport.DatabaseTransport"
CELERY_ALWAYS_EAGER = True
djcelery.setup_loader()
...@@ -48,7 +48,7 @@ def root_redirect(request): ...@@ -48,7 +48,7 @@ def root_redirect(request):
try: try:
root = Article.get_root() root = Article.get_root()
except: except:
err = not_found(request, 'mainpage') err = not_found(request, '/')
return err return err
return HttpResponseRedirect(reverse('wiki_view', args=(root.get_url()))) return HttpResponseRedirect(reverse('wiki_view', args=(root.get_url())))
...@@ -92,7 +92,7 @@ def create(request, wiki_url): ...@@ -92,7 +92,7 @@ def create(request, wiki_url):
#except ShouldHaveExactlyOneRootSlug, (e): #except ShouldHaveExactlyOneRootSlug, (e):
except: except:
if Article.objects.filter(parent=None).count() > 0: if Article.objects.filter(parent=None).count() > 0:
return HttpResponseRedirect(reverse('wiki_view', args=('',))) return HttpResponseRedirect(reverse('wiki_view', args=('/',)))
# Root not found... # Root not found...
path = [] path = []
url_path = [""] url_path = [""]
...@@ -380,7 +380,7 @@ def fetch_from_url(request, url): ...@@ -380,7 +380,7 @@ def fetch_from_url(request, url):
try: try:
root = Article.get_root() root = Article.get_root()
except: except:
err = not_found(request, '') err = not_found(request, '/')
return (article, path, err) return (article, path, err)
if url_path and root.slug == url_path[0]: if url_path and root.slug == url_path[0]:
......
...@@ -8,7 +8,12 @@ from django.shortcuts import redirect ...@@ -8,7 +8,12 @@ from django.shortcuts import redirect
from django.core.context_processors import csrf from django.core.context_processors import csrf
#valid_templates=['index.html', 'staff.html', 'info.html', 'credits.html'] #valid_templates=['index.html', 'staff.html', 'info.html', 'credits.html']
valid_templates=['mitx_global.html', 'index.html'] valid_templates=['mitx_global.html',
'index.html',
'tos.html',
'privacy.html',
'honor.html',
'copyright.html']
def index(request, template): def index(request, template):
csrf_token = csrf(request)['csrf_token'] csrf_token = csrf(request)['csrf_token']
......
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