Files
edx-platform/djangoapps/courseware/module_render.py
Calen Pennington 8575ed439d Merge branch 'master' into dogfood
Conflicts:
	djangoapps/courseware/capa/responsetypes.py
	djangoapps/courseware/module_render.py
	djangoapps/courseware/modules/html_module.py
	djangoapps/courseware/modules/seq_module.py
	djangoapps/courseware/modules/x_module.py
	djangoapps/courseware/views.py
	templates/main.html
	templates/problem.js
	templates/textbox.html
2012-06-05 14:38:49 -04:00

256 lines
9.8 KiB
Python

import json
import logging
from lxml import etree
from django.http import Http404
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 django.conf import settings
from mitxmako.shortcuts import render_to_string
from models import StudentModule
from multicourse import multicourse_settings
import courseware.modules
import courseware.content_parser as content_parser
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)
'''
def __init__(self, ajax_url, track_function, render_function, filestore=None):
self.ajax_url = ajax_url
self.track_function = track_function
if not filestore:
self.filestore = OSFS(settings.DATA_DIR)
else:
self.filestore = filestore
if settings.DEBUG:
log.info("[courseware.module_render.I4xSystem] filestore path = %s" % filestore)
self.render_function = render_function
self.exception404 = Http404
self.DEBUG = settings.DEBUG
def get(self,attr): # uniform access to attributes (like etree)
return self.__dict__.get(attr)
def set(self,attr,val): # 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 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
# is broken.
for o in cache:
if o.module_type == module_type and \
o.module_id == module_id:
return o
return None
def make_track_function(request):
''' We want the capa problem (and other modules) to be able to
track/log what happens inside them without adding dependencies on
Django or the rest of the codebase. We do this by passing a
tracking function to them. This generates a closure for each request
that gives a clean interface on both sides.
'''
import track.views
def f(event_type, event):
return track.views.server_track(request, event_type, event, page='x_module')
return f
def grade_histogram(module_id):
''' Print out a histogram of grades on a given problem.
Part of staff member debug info.
'''
from django.db import connection
cursor = connection.cursor()
cursor.execute("select courseware_studentmodule.grade,COUNT(courseware_studentmodule.student_id) from courseware_studentmodule where courseware_studentmodule.module_id=%s group by courseware_studentmodule.grade", [module_id])
grades = list(cursor.fetchall())
grades.sort(key=lambda x:x[0]) # Probably not necessary
if (len(grades) == 1 and grades[0][0] is None):
return []
return grades
def get_module(user, request, xml_module, module_object_preload, position=None):
module_type=xml_module.tag
module_class=courseware.modules.get_module_class(module_type)
module_id=xml_module.get('id') #module_class.id_attribute) or ""
# Grab state from database
smod = object_cache(module_object_preload,
user,
module_type,
module_id)
if not smod: # If nothing in the database...
state=None
else:
state = smod.state
# get coursename if stored
coursename = multicourse_settings.get_coursename_from_request(request)
if coursename and settings.ENABLE_MULTICOURSE:
xp = multicourse_settings.get_course_xmlpath(coursename) # path to XML for the course
data_root = settings.DATA_DIR + xp
else:
data_root = settings.DATA_DIR
# Create a new instance
ajax_url = settings.MITX_ROOT_URL + '/modx/'+module_type+'/'+module_id+'/'
system = I4xSystem(track_function = make_track_function(request),
render_function = lambda x: render_x_module(user, request, x, module_object_preload, position),
ajax_url = ajax_url,
filestore = OSFS(data_root),
)
system.set('position',position) # pass URL specified position along to module, through I4xSystem
instance=module_class(system,
etree.tostring(xml_module),
module_id,
state=state)
# If instance wasn't already in the database, and this
# isn't a guest user, create it
if not smod and user.is_authenticated():
smod=StudentModule(student=user,
module_type = module_type,
module_id=module_id,
state=instance.get_state())
smod.save()
module_object_preload.append(smod)
return (instance, smod, module_type)
def render_x_module(user, request, xml_module, module_object_preload, position=None):
''' Generic module for extensions. This renders to HTML.
modules include sequential, vertical, problem, video, html
Note that modules can recurse. problems, video, html, can be inside sequential or vertical.
Arguments:
- user : current django User
- request : current django HTTPrequest
- xml_module : lxml etree of xml subtree for the current module
- module_object_preload : list of StudentModule objects, one of which may match this module type and id
- position : extra information from URL for user-specified position within module
Returns:
- dict which is context for HTML rendering of the specified module
'''
if xml_module==None :
return {"content":""}
(instance, smod, module_type) = get_module(user, request, xml_module, module_object_preload, position)
# Grab content
content = instance.get_html()
# special extra information about each problem, only for users who are staff
if user.is_staff:
module_id = xml_module.get('id')
histogram = grade_histogram(module_id)
render_histogram = len(histogram) > 0
content=content+render_to_string("staff_problem_info.html", {'xml':etree.tostring(xml_module),
'module_id' : module_id,
'histogram': json.dumps(histogram),
'render_histogram' : render_histogram})
content = {'content':content,
'type':module_type}
return content
def modx_dispatch(request, module=None, dispatch=None, id=None):
''' Generic view for extensions. This is where AJAX calls go.'''
if not request.user.is_authenticated():
return redirect('/')
# Grab the student information for the module from the database
s = StudentModule.objects.filter(student=request.user,
module_id=id)
#s = StudentModule.get_with_caching(request.user, id)
if len(s) == 0 or s is None:
log.debug("Couldnt find module for user and id " + str(module) + " " + str(request.user) + " "+ str(id))
raise Http404
s = s[0]
oldgrade = s.grade
oldstate = s.state
dispatch=dispatch.split('?')[0]
ajax_url = settings.MITX_ROOT_URL + '/modx/'+module+'/'+id+'/'
# get coursename if stored
coursename = multicourse_settings.get_coursename_from_request(request)
if coursename and settings.ENABLE_MULTICOURSE:
xp = multicourse_settings.get_course_xmlpath(coursename) # path to XML for the course
data_root = settings.DATA_DIR + xp
else:
data_root = settings.DATA_DIR
# Grab the XML corresponding to the request from course.xml
try:
xml = content_parser.module_xml(request.user, module, 'id', id, coursename)
except:
log.exception("Unable to load module during ajax call. module=%s, dispatch=%s, id=%s" % (module, dispatch, id))
if accepts(request, 'text/html'):
return render_to_response("module-error.html", {})
else:
response = HttpResponse(json.dumps({'success': "We're sorry, this module is temporarily unavailable. Our staff is working to fix it as soon as possible"}))
return response
# Create the module
system = I4xSystem(track_function = make_track_function(request),
render_function = None,
ajax_url = ajax_url,
filestore = OSFS(data_root),
)
try:
instance=courseware.modules.get_module_class(module)(system,
xml,
id,
state=oldstate)
except:
log.exception("Unable to load module instance during ajax call")
if accepts(request, 'text/html'):
return render_to_response("module-error.html", {})
else:
response = HttpResponse(json.dumps({'success': "We're sorry, this module is temporarily unavailable. Our staff is working to fix it as soon as possible"}))
return response
# Let the module handle the AJAX
ajax_return=instance.handle_ajax(dispatch, request.POST)
# Save the state back to the database
s.state=instance.get_state()
if instance.get_score():
s.grade=instance.get_score()['score']
if s.grade != oldgrade or s.state != oldstate:
s.save()
# Return whatever the module wanted to return to the client/caller
return HttpResponse(ajax_return)