From 07454b82b2fa9e6283bdd60b0fdce67f82df312f Mon Sep 17 00:00:00 2001 From: Piotr Mitros Date: Fri, 16 Dec 2011 15:51:10 -0500 Subject: [PATCH] Factored out a couple of modules --HG-- rename : courseware/static/schematic.js => courseware/static/js/schematic.js --- auth/models.py | 4 +- courseware/capa_module.py | 34 ++++- courseware/capa_problem.py | 5 + courseware/html_module.py | 33 +++++ courseware/module_render.py | 166 ++++++++++++++++++++++++ courseware/profile.py | 48 +++++++ courseware/static/{ => js}/schematic.js | 0 courseware/video_module.py | 13 +- courseware/views.py | 127 +----------------- courseware/x_module.py | 6 +- static_template_view/views.py | 2 +- 11 files changed, 289 insertions(+), 149 deletions(-) create mode 100644 courseware/html_module.py create mode 100644 courseware/module_render.py create mode 100644 courseware/profile.py rename courseware/static/{ => js}/schematic.js (100%) diff --git a/auth/models.py b/auth/models.py index 4695bef5cb..95471e4e12 100644 --- a/auth/models.py +++ b/auth/models.py @@ -5,13 +5,13 @@ import uuid class UserProfile(models.Model): ## CRITICAL TODO/SECURITY # Sanitize all fields. - # This is not visible to other users, but could introduce holes - # later + # This is not visible to other users, but could introduce holes later user = models.ForeignKey(User, unique=True, db_index=True) name = models.TextField(blank=True) language = models.TextField(blank=True) location = models.TextField(blank=True) meta = models.TextField(blank=True) # JSON dictionary for future expansion + courseware = models.TextField(blank=True) class Registration(models.Model): ''' Allows us to wait for e-mail before user is registered. A diff --git a/courseware/capa_module.py b/courseware/capa_module.py index 617e33d31e..986f2f2147 100644 --- a/courseware/capa_module.py +++ b/courseware/capa_module.py @@ -6,6 +6,10 @@ from x_module import XModule from capa_problem import LoncapaProblem +import dateutil +import datetime + + from xml.dom.minidom import parse, parseString ## TODO: Abstract out from Django @@ -18,8 +22,13 @@ class LoncapaModule(XModule): prupose now. We can e.g .destroy and create the capa_problem on a reset. ''' - xml_tags=["problem"] - id_attribute="filename" + xml_tags = ["problem"] + id_attribute = "filename" + + attempts = None + max_attempts = None + + due_date = None def get_state(self): return self.lcp.get_state() @@ -66,18 +75,33 @@ class LoncapaModule(XModule): XModule.__init__(self, xml, item_id, ajax_url, track_url, state) dom=parseString(xml) node=dom.childNodes[0] + + self.due_date=node.getAttribute("due") + if len(self.due_date)>0: + self.due_date=dateutil.parser.parse(self.due_date) + else: + self.due_date=None + + self.max_attempts=node.getAttribute("attempts") + if len(self.max_attempts)>0: + self.max_attempts=int(self.max_attempts) + else: + self.max_attempts=None + self.filename=node.getAttribute("filename") filename=settings.DATA_DIR+self.filename+".xml" self.name=node.getAttribute("name") self.lcp=LoncapaProblem(filename, self.item_id, state) def handle_ajax(self, dispatch, get): - if dispatch=='problem_check': + if dispatch=='problem_get': + response = self.get_problem(get) + elif False: #self.due_date > + return json.dumps({"error":"Past due date"}) + elif dispatch=='problem_check': response = self.check_problem(get) elif dispatch=='problem_reset': response = self.reset_problem(get) - elif dispatch=='problem_get': - response = self.get_problem(get) else: return "Error" return response diff --git a/courseware/capa_problem.py b/courseware/capa_problem.py index 072565acd4..4012873a5b 100644 --- a/courseware/capa_problem.py +++ b/courseware/capa_problem.py @@ -1,4 +1,5 @@ import random, numpy, math, scipy, sys, StringIO, os, struct, json +from dateutil import parser from xml.dom.minidom import parse, parseString @@ -198,6 +199,7 @@ class LoncapaProblem(): return html def grade_fr(self, question, answer): + print question, answer correct = True for i in range(question['samples_count']): instructor_variables = strip_dict(dict(self.context)) @@ -208,6 +210,9 @@ class LoncapaProblem(): student_variables[str(var)] = value instructor_result = evaluator(instructor_variables,{},str(question['answer'])) student_result = evaluator(student_variables,{},str(answer)) + print student_result, instructor_result + if math.isnan(student_result) or math.isinf(student_result): + return "incorrect" if abs( student_result - instructor_result ) > question['tolerance']: return "incorrect" diff --git a/courseware/html_module.py b/courseware/html_module.py new file mode 100644 index 0000000000..825c182e66 --- /dev/null +++ b/courseware/html_module.py @@ -0,0 +1,33 @@ +from x_module import XModule + +from xml.dom.minidom import parse, parseString + +import json + +## TODO: Abstract out from Django +from django.conf import settings +from djangomako.shortcuts import render_to_response, render_to_string + +class HtmlModule(XModule): + id_attribute = 'filename' + + def get_state(self): + return json.dumps({ }) + + def get_xml_tags(): + return "html" + + def get_html(self): + print "XX",self.item_id + return render_to_string(self.item_id, {'id': self.item_id}) + + def __init__(self, xml, item_id, ajax_url=None, track_url=None, state=None): + print "item id" , item_id + XModule.__init__(self, xml, item_id, ajax_url, track_url, state) + +# template_source=module.getAttribute('filename') +# return {'content':render_to_string(template_source, {})} + + # print state + # if state!=None and "time" not in json.loads(state): + # self.video_time = 0 diff --git a/courseware/module_render.py b/courseware/module_render.py new file mode 100644 index 0000000000..123b6fdbc1 --- /dev/null +++ b/courseware/module_render.py @@ -0,0 +1,166 @@ +from django.http import HttpResponse +from django.template import Context, loader +from djangomako.shortcuts import render_to_response, render_to_string +from xml.dom.minidom import parse, parseString +import json, os, sys +from django.core.context_processors import csrf + +from django.template import Context +from django.contrib.auth.models import User +from auth.models import UserProfile +from django.shortcuts import redirect + +import StringIO + +from django.http import Http404 + +import urllib + +import capa_module +import video_module +import html_module + +from models import StudentModule + +import urllib + +from django.conf import settings + +import content_parser + +import uuid + +#def html_module(request, module): +# ''' Show basic text +# ''' +# template_source=module.getAttribute('filename') +# return {'content':render_to_string(template_source, {})} + +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] + js="".join([e[1]['init_js'] for e in contents if 'init_js' in e[1]]) + + return {'init_js':js, + "destroy_js":"", + 'content':render_to_string('vert_module.html',{'items':contents})} + +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 tags so they don't break + # mid-string + if 'init_js' not in m: m['init_js']="" + content=json.dumps(m['content']) + content=content.replace('', '<"+"/script>') + return {'content':content, + "destroy_js":"", + 'init_js':m['init_js']} + contents=[(e.getAttribute("name"),j(render_module(request, e))) \ + for e in module.childNodes \ + if e.nodeType==1] + + js="".join([e[1]['init_js'] for e in contents if 'init_js' in e[1]]) + + iid=uuid.uuid1().hex + + params={'items':contents, + 'id':"seq"} + + print module.nodeName + if module.nodeName == 'sequential': + return {'init_js':js+render_to_string('seq_module.js',params), + "destroy_js":"", + 'content':render_to_string('seq_module.html',params)} + if module.nodeName == 'tab': + return {'init_js':js+render_to_string('tab_module.js',params), + "destroy_js":"", + 'content':render_to_string('tab_module.html',params)} + + +modx_modules={'problem':capa_module.LoncapaModule, + 'video':video_module.VideoModule, + 'html':html_module.HtmlModule} + +def render_x_module(request, xml_module): + ''' Generic module for extensions. This renders to HTML. ''' + # Check if problem has an instance in DB + module_type=xml_module.nodeName + module_class=modx_modules[module_type] + print "mida",module_class.id_attribute + module_id=xml_module.getAttribute(module_class.id_attribute) + print "mid",module_id + + # Grab state from database + s = StudentModule.objects.filter(student=request.user, + module_id=module_id, + module_type = module_type) + if len(s) == 0: # If nothing in the database... + state=None + else: + smod = s[0] + state = smod.state + + # Create a new instance + ajax_url = '/modx/'+module_type+'/'+module_id+'/' + instance=module_class(xml_module.toxml(), + module_id, + ajax_url=ajax_url, + state=state) + + # If instance wasn't already in the database, create it + if len(s) == 0: + smod=StudentModule(student=request.user, + module_type = module_type, + module_id=module_id, + state=instance.get_state(), + xml=instance.xml) + # Grab content + content = {'content':instance.get_html(), + "destroy_js":instance.get_destroy_js(), + 'init_js':instance.get_init_js()} + + smod.save() # This may be optional (at least in the case of no instance in the dB) + + return content + +def modx_dispatch(request, module=None, dispatch=None, id=None): + ''' Generic module for extensions. ''' + s = StudentModule.objects.filter(module_type=module, student=request.user, module_id=id) + if len(s) == 0: + raise Http404 + + s=s[0] + + dispatch=dispatch.split('?')[0] + + ajax_url = '/modx/'+module+'/'+id+'/' + + instance=modx_modules[module](s.xml, s.module_id, ajax_url=ajax_url, state=s.state) + html=instance.handle_ajax(dispatch, request.GET) + s.state=instance.get_state() + s.grade=instance.get_score()['score'] + s.save() + return HttpResponse(html) + +module_types={'video':render_x_module, + 'html':render_x_module, + 'tab':seq_module, + 'vertical':vertical_module, + 'sequential':seq_module, + 'problem':render_x_module, + } + #'lab':lab_module, + +def render_module(request, module): + ''' Generic dispatch for internal modules. ''' + if module==None: + return {"content":""} + if str(module.localName) in module_types: + return module_types[module.localName](request, module) + raise Http404 diff --git a/courseware/profile.py b/courseware/profile.py new file mode 100644 index 0000000000..6f7cb1ede7 --- /dev/null +++ b/courseware/profile.py @@ -0,0 +1,48 @@ +def profile(request): + ''' User profile. Show username, location, etc, as well as grades . + We need to allow the user to change some of these settings .''' + if not request.user.is_authenticated(): + return redirect('/') + + dom=parse(settings.DATA_DIR+'course.xml') + hw=[] + course = dom.getElementsByTagName('course')[0] + chapters = course.getElementsByTagName('chapter') + + responses=StudentModule.objects.filter(student=request.user) + + for c in chapters: + for s in c.getElementsByTagName('section'): + problems=s.getElementsByTagName('problem') + scores=[] + if len(problems)>0: + for p in problems: + id = p.getAttribute('filename') + correct = 0 + for response in responses: + if response.module_id == id: + if response.grade!=None: + correct=response.grade + else: + 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? + scores.append((int(correct),total)) + score={'course':course.getAttribute('name'), + 'section':s.getAttribute("name"), + 'chapter':c.getAttribute("name"), + 'scores':scores, + } + hw.append(score) + + user_info=UserProfile.objects.get(user=request.user) + + context={'name':user_info.name, + 'username':request.user.username, + 'location':user_info.location, + 'language':user_info.language, + 'email':request.user.email, + 'homeworks':hw, + 'csrf':csrf(request)['csrf_token'] + } + return render_to_response('profile.html', context) + diff --git a/courseware/static/schematic.js b/courseware/static/js/schematic.js similarity index 100% rename from courseware/static/schematic.js rename to courseware/static/js/schematic.js diff --git a/courseware/video_module.py b/courseware/video_module.py index d8a19be474..a8889bd534 100644 --- a/courseware/video_module.py +++ b/courseware/video_module.py @@ -1,6 +1,3 @@ -# For calculator: -# http://pyparsing.wikispaces.com/file/view/fourFn.py - from x_module import XModule from xml.dom.minidom import parse, parseString @@ -12,10 +9,6 @@ from django.conf import settings from djangomako.shortcuts import render_to_response, render_to_string class VideoModule(XModule): - ''' Implements a generic learning module. - Initialized on access with __init__, first time with state=None, and - then with state - ''' id_attribute = 'youtube' video_time = 0 @@ -31,12 +24,8 @@ class VideoModule(XModule): def get_xml_tags(): ''' Tags in the courseware file guaranteed to correspond to the module ''' - return "video1" + return "video" - def get_id_attribute(): - ''' An attribute in the XML scheme that is guaranteed unique. ''' - return "youtube" - def get_html(self): return render_to_string('video.html',{'id':self.item_id, 'time':self.video_time}) diff --git a/courseware/views.py b/courseware/views.py index 0712407d4b..09ad29e39b 100644 --- a/courseware/views.py +++ b/courseware/views.py @@ -29,6 +29,8 @@ import content_parser import uuid +from module_render import * + template_imports={'urllib':urllib} def profile(request): @@ -98,131 +100,6 @@ def render_accordion(request,course,chapter,section): return {'init_js':render_to_string('accordion_init.js',context), 'content':render_to_string('accordion.html',context)} -def html_module(request, module): - ''' Show basic text - ''' - template_source=module.getAttribute('filename') - return {'content':render_to_string(template_source, {})} - -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] - js="".join([e[1]['init_js'] for e in contents if 'init_js' in e[1]]) - - return {'init_js':js, - 'content':render_to_string('vert_module.html',{'items':contents})} - -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 tags so they don't break - # mid-string - if 'init_js' not in m: m['init_js']="" - content=json.dumps(m['content']) - content=content.replace('', '<"+"/script>') - return {'content':content, 'init_js':m['init_js']} - contents=[(e.getAttribute("name"),j(render_module(request, e))) \ - for e in module.childNodes \ - if e.nodeType==1] - - js="".join([e[1]['init_js'] for e in contents if 'init_js' in e[1]]) - - iid=uuid.uuid1().hex - - params={'items':contents, - 'id':"seq"} - - print module.nodeName - if module.nodeName == 'sequential': - return {'init_js':js+render_to_string('seq_module.js',params), - 'content':render_to_string('seq_module.html',params)} - if module.nodeName == 'tab': - return {'init_js':js+render_to_string('tab_module.js',params), - 'content':render_to_string('tab_module.html',params)} - - -modx_modules={'problem':capa_module.LoncapaModule, 'video':video_module.VideoModule} - -def render_x_module(request, xml_module): - ''' Generic module for extensions. This renders to HTML. ''' - # Check if problem has an instance in DB - module_type=xml_module.nodeName - module_class=modx_modules[module_type] - module_id=xml_module.getAttribute(module_class.id_attribute) - - # Grab state from database - s = StudentModule.objects.filter(student=request.user, - module_id=module_id, - module_type = module_type) - if len(s) == 0: # If nothing in the database... - state=None - else: - smod = s[0] - state = smod.state - - # Create a new instance - ajax_url = '/modx/'+module_type+'/'+module_id+'/' - instance=module_class(xml_module.toxml(), - module_id, - ajax_url=ajax_url, - state=state) - - # If instance wasn't already in the database, create it - if len(s) == 0: - smod=StudentModule(student=request.user, - module_type = module_type, - module_id=module_id, - state=instance.get_state(), - xml=instance.xml) - # Grab content - content = {'content':instance.get_html(), - 'init_js':instance.get_init_js()} - - smod.save() # This may be optional (at least in the case of no instance in the dB) - - return content - -def modx_dispatch(request, module=None, dispatch=None, id=None): - ''' Generic module for extensions. ''' - s = StudentModule.objects.filter(module_type=module, student=request.user, module_id=id) - if len(s) == 0: - raise Http404 - - s=s[0] - - dispatch=dispatch.split('?')[0] - - ajax_url = '/modx/'+module+'/'+id+'/' - - instance=modx_modules[module](s.xml, s.module_id, ajax_url=ajax_url, state=s.state) - html=instance.handle_ajax(dispatch, request.GET) - s.state=instance.get_state() - s.grade=instance.get_score()['score'] - s.save() - return HttpResponse(html) - -module_types={'video':render_x_module, - 'html':html_module, - 'tab':seq_module, - 'vertical':vertical_module, - 'sequential':seq_module, - 'problem':render_x_module, - } - #'lab':lab_module, - -def render_module(request, module): - ''' Generic dispatch for internal modules. ''' - if module==None: - return {"content":""} - if str(module.localName) in module_types: - return module_types[module.localName](request, module) - return {"content":""} - def index(request, course="6.002 Spring 2012", chapter="Using the System", section="Hints"): ''' Displays courseware accordion, and any associated content. ''' diff --git a/courseware/x_module.py b/courseware/x_module.py index 73b98a658f..9773bf74af 100644 --- a/courseware/x_module.py +++ b/courseware/x_module.py @@ -3,14 +3,12 @@ class XModule: Initialized on access with __init__, first time with state=None, and then with state ''' + id_attribute='name' # An attribute guaranteed to be unique + def get_xml_tags(): ''' Tags in the courseware file guaranteed to correspond to the module ''' return [] - def get_id_attribute(): - ''' An attribute in the XML scheme that is guaranteed unique. ''' - return "name" - def get_state(self): return "" diff --git a/static_template_view/views.py b/static_template_view/views.py index 88da56333c..65633eb9b4 100644 --- a/static_template_view/views.py +++ b/static_template_view/views.py @@ -6,7 +6,7 @@ from djangomako.shortcuts import render_to_response, render_to_string from django.shortcuts import redirect -valid_templates=['index.html', 'staff.html', 'info.html'] +valid_templates=['index.html', 'staff.html', 'info.html', 'credits.html'] def index(request, template): if template in valid_templates: