diff --git a/auth/models.py b/auth/models.py index 1a7bf6c221..88cdd2da61 100644 --- a/auth/models.py +++ b/auth/models.py @@ -1,6 +1,7 @@ +import uuid + from django.db import models from django.contrib.auth.models import User -import uuid class UserProfile(models.Model): ## CRITICAL TODO/SECURITY diff --git a/auth/views.py b/auth/views.py index 61419e507b..f2f945644f 100644 --- a/auth/views.py +++ b/auth/views.py @@ -1,16 +1,21 @@ -from djangomako.shortcuts import render_to_response, render_to_string -from django.contrib.auth.models import User -from django.shortcuts import redirect +import json +import logging +import random +import string + +from django.conf import settings from django.contrib.auth import logout, authenticate, login from django.contrib.auth.models import User -from django.http import HttpResponse -import json -from models import Registration, UserProfile -from django.conf import settings +from django.contrib.auth.models import User from django.core.context_processors import csrf from django.core.validators import validate_email, validate_slug -import random, string from django.db import connection +from django.http import HttpResponse +from django.shortcuts import redirect +from mitxmako.shortcuts import render_to_response, render_to_string +from models import Registration, UserProfile + +log = logging.getLogger("mitx.auth") def csrf_token(context): csrf_token = context.get('csrf_token', '') @@ -37,37 +42,43 @@ def index(request): # return render_to_response('courseinfo.html', {'error' : '', # 'csrf': csrf_token }) +# Need different levels of logging def login_user(request, error=""): -# print request.POST if 'email' not in request.POST or 'password' not in request.POST: -# print "X" return render_to_response('login.html', {'error':error.replace('+',' ')}) + email = request.POST['email'] password = request.POST['password'] try: - user=User.objects.get(email=email) + user = User.objects.get(email=email) except User.DoesNotExist: + log.warning("Login failed - Unknown user email: {0}".format(email)) return HttpResponse(json.dumps({'success':False, 'error': 'Invalid login'})) # TODO: User error message - username=user.username - user=authenticate(username=username, password=password) + username = user.username + user = authenticate(username=username, password=password) if user is None: + log.warning("Login failed - password for {0} is invalid".format(email)) return HttpResponse(json.dumps({'success':False, 'error': 'Invalid login'})) + if user is not None and user.is_active: - login(request, user) - if request.POST['remember'] == 'true': - request.session.set_expiry(None) # or change to 604800 for 7 days -# print "recall" - else: - request.session.set_expiry(0) - #print "close" -# print len(connection.queries), connection.queries + try: + login(request, user) + if request.POST['remember'] == 'true': + request.session.set_expiry(None) # or change to 604800 for 7 days + log.debug("Setting user session to never expire") + else: + request.session.set_expiry(0) + except Exception as e: + log.critical("Login failed - Could not create session. Is memcached running?") + log.exception(e) + + log.info("Login success - {0} ({1})".format(username, email)) return HttpResponse(json.dumps({'success':True})) -# print len(connection.queries), connection.queries - + log.warning("Login failed - Account not active for user {0}".format(username)) return HttpResponse(json.dumps({'success':False, 'error': 'Account not active. Check your e-mail.'})) diff --git a/circuit/models.py b/circuit/models.py index ada1a5f295..cc103e1af8 100644 --- a/circuit/models.py +++ b/circuit/models.py @@ -1,6 +1,7 @@ +import uuid + from django.db import models from django.contrib.auth.models import User -import uuid class ServerCircuit(models.Model): # Later, add owner, who can edit, part of what app, etc. diff --git a/circuit/views.py b/circuit/views.py index 235c25b69f..96fcc83115 100644 --- a/circuit/views.py +++ b/circuit/views.py @@ -1,24 +1,26 @@ -from djangomako.shortcuts import render_to_response, render_to_string -from django.shortcuts import redirect +import json import os + +import xml.etree.ElementTree + from django.conf import settings from django.http import Http404 -from models import ServerCircuit -import json -import xml.etree.ElementTree from django.http import HttpResponse +from django.shortcuts import redirect +from mitxmako.shortcuts import render_to_response, render_to_string + +from models import ServerCircuit def circuit_line(circuit): + ''' Returns string for an appropriate input element for a circuit. + TODO: Rename. ''' if not circuit.isalnum(): raise Http404() try: sc = ServerCircuit.objects.get(name=circuit) schematic = sc.schematic - print "Got" except: schematic = '' - print "not got" - print "X", schematic circuit_line = xml.etree.ElementTree.Element('input') circuit_line.set('type', 'hidden') diff --git a/courseware/capa/calc.py b/courseware/capa/calc.py index ef34e186e2..bd4136d393 100644 --- a/courseware/capa/calc.py +++ b/courseware/capa/calc.py @@ -1,5 +1,6 @@ import math import operator + from pyparsing import Word, alphas, nums, oneOf, Literal from pyparsing import ZeroOrMore, OneOrMore, StringStart from pyparsing import StringEnd, Optional, Forward diff --git a/courseware/capa/capa_problem.py b/courseware/capa/capa_problem.py index 46ad5e861d..b7ac8940fe 100644 --- a/courseware/capa/capa_problem.py +++ b/courseware/capa/capa_problem.py @@ -1,18 +1,24 @@ -import random, numpy, math, scipy -import struct, os +import copy +import math +import numpy +import os +import random import re +import scipy +import struct + from lxml import etree from lxml.etree import Element -import copy + from mako.template import Template -from courseware.content_parser import xpath_remove -import calc, eia from util import contextualize_text - from inputtypes import textline, schematic from responsetypes import numericalresponse, formularesponse, customresponse, schematicresponse +import calc +import eia + response_types = {'numericalresponse':numericalresponse, 'formularesponse':formularesponse, 'customresponse':customresponse, @@ -52,12 +58,14 @@ class LoncapaProblem(object): self.done = False self.filename = filename - if id!=None: + if id: self.problem_id = id else: - self.problem_id = filename + print "NO ID" + raise Exception("This should never happen (183)") + #self.problem_id = filename - if state!=None: + if state: if 'seed' in state: self.seed = state['seed'] if 'student_answers' in state: @@ -68,7 +76,7 @@ class LoncapaProblem(object): self.done = state['done'] # TODO: Does this deplete the Linux entropy pool? Is this fast enough? - if self.seed == None: + if not self.seed: self.seed=struct.unpack('i', os.urandom(4))[0] ## Parse XML file @@ -102,7 +110,7 @@ class LoncapaProblem(object): for key in self.correct_map: if self.correct_map[key] == u'correct': correct += 1 - if self.student_answers == None or len(self.student_answers)==0: + if (not self.student_answers) or len(self.student_answers)==0: return {'score':0, 'total':self.get_max_score()} else: @@ -132,8 +140,8 @@ class LoncapaProblem(object): for entry in problems_simple.xpath("//"+"|//".join(response_properties+entry_types)): answer = entry.get('correct_answer') - if answer != None: - answer_map[entry.get('id')] = contextualize_text(answer, self.context()) + if answer: + answer_map[entry.get('id')] = contextualize_text(answer, self.context) return answer_map @@ -162,7 +170,7 @@ class LoncapaProblem(object): status = self.correct_map[problemtree.get('id')] value = "" - if self.student_answers != None and problemtree.get('id') in self.student_answers: + if self.student_answers and problemtree.get('id') in self.student_answers: value = self.student_answers[problemtree.get('id')] return html_special_response[problemtree.tag](problemtree, value, status) #TODO @@ -170,7 +178,7 @@ class LoncapaProblem(object): tree=Element(problemtree.tag) for item in problemtree: subitems = self.extract_html(item) - if subitems != None: + if subitems: for subitem in subitems: tree.append(subitem) for (key,value) in problemtree.items(): diff --git a/courseware/capa/inputtypes.py b/courseware/capa/inputtypes.py index db98363872..c7732014da 100644 --- a/courseware/capa/inputtypes.py +++ b/courseware/capa/inputtypes.py @@ -1,8 +1,8 @@ -from djangomako.shortcuts import render_to_response, render_to_string - from lxml.etree import Element from lxml import etree +from mitxmako.shortcuts import render_to_response, render_to_string + class textline(object): @staticmethod def render(element, value, state): diff --git a/courseware/capa/responsetypes.py b/courseware/capa/responsetypes.py index 3e3cefd8ab..93ebac85f9 100644 --- a/courseware/capa/responsetypes.py +++ b/courseware/capa/responsetypes.py @@ -1,14 +1,23 @@ -import random, numpy, math, scipy, json -from util import contextualize_text +import json +import math +import numpy +import random +import scipy + from calc import evaluator -import random, math from django.conf import settings +from util import contextualize_text + +import calc +import eia # TODO: Should be the same object as in capa_problem global_context={'random':random, 'numpy':numpy, 'math':math, - 'scipy':scipy} + 'scipy':scipy, + 'calc':calc, + 'eia':eia} class numericalresponse(object): def __init__(self, xml, context): diff --git a/courseware/capa/unit.py b/courseware/capa/unit.py new file mode 100644 index 0000000000..2cbe4f93f0 --- /dev/null +++ b/courseware/capa/unit.py @@ -0,0 +1,132 @@ +import math +import operator + +from numpy import eye, array + +from pyparsing import Word, alphas, nums, oneOf, Literal +from pyparsing import ZeroOrMore, OneOrMore, StringStart +from pyparsing import StringEnd, Optional, Forward +from pyparsing import CaselessLiteral, Group, StringEnd +from pyparsing import NoMatch, stringEnd + +base_units = ['meter', 'gram', 'second', 'ampere', 'kelvin', 'mole', 'cd'] +unit_vectors = dict([(base_units[i], eye(len(base_units))[:,i]) for i in range(len(base_units))]) + + +def unit_evaluator(unit_string, units=unit_map): + ''' Evaluate an expression. Variables are passed as a dictionary + from string to value. Unary functions are passed as a dictionary + from string to function ''' + if string.strip() == "": + return float('nan') + ops = { "^" : operator.pow, + "*" : operator.mul, + "/" : operator.truediv, + } + prefixes={'%':0.01,'k':1e3,'M':1e6,'G':1e9, + 'T':1e12,#'P':1e15,'E':1e18,'Z':1e21,'Y':1e24, + 'c':1e-2,'m':1e-3,'u':1e-6, + 'n':1e-9,'p':1e-12}#,'f':1e-15,'a':1e-18,'z':1e-21,'y':1e-24} + + def super_float(text): + ''' Like float, but with si extensions. 1k goes to 1000''' + if text[-1] in suffixes: + return float(text[:-1])*suffixes[text[-1]] + else: + return float(text) + + def number_parse_action(x): # [ '7' ] -> [ 7 ] + return [super_float("".join(x))] + def exp_parse_action(x): # [ 2 ^ 3 ^ 2 ] -> 512 + x = [e for e in x if type(e) == float] # Ignore ^ + x.reverse() + x=reduce(lambda a,b:b**a, x) + return x + def parallel(x): # Parallel resistors [ 1 2 ] => 2/3 + if len(x) == 1: + return x[0] + if 0 in x: + return float('nan') + x = [1./e for e in x if type(e) == float] # Ignore ^ + return 1./sum(x) + def sum_parse_action(x): # [ 1 + 2 - 3 ] -> 0 + total = 0.0 + op = ops['+'] + for e in x: + if e in set('+-'): + op = ops[e] + else: + total=op(total, e) + return total + def prod_parse_action(x): # [ 1 * 2 / 3 ] => 0.66 + prod = 1.0 + op = ops['*'] + for e in x: + if e in set('*/'): + op = ops[e] + else: + prod=op(prod, e) + return prod + def func_parse_action(x): + return [functions[x[0]](x[1])] + + number_suffix=reduce(lambda a,b:a|b, map(Literal,suffixes.keys()), NoMatch()) # SI suffixes and percent + (dot,minus,plus,times,div,lpar,rpar,exp)=map(Literal,".-+*/()^") + + number_part=Word(nums) + inner_number = ( number_part+Optional("."+number_part) ) | ("."+number_part) # 0.33 or 7 or .34 + number=Optional(minus | plus)+ inner_number + \ + Optional(CaselessLiteral("E")+Optional("-")+number_part)+ \ + Optional(number_suffix) # 0.33k or -17 + number=number.setParseAction( number_parse_action ) # Convert to number + + # Predefine recursive variables + expr = Forward() + factor = Forward() + + def sreduce(f, l): + ''' Same as reduce, but handle len 1 and len 0 lists sensibly ''' + if len(l)==0: + return NoMatch() + if len(l)==1: + return l[0] + return reduce(f, l) + + # Handle variables passed in. E.g. if we have {'R':0.5}, we make the substitution. + # Special case for no variables because of how we understand PyParsing is put together + if len(variables)>0: + varnames = sreduce(lambda x,y:x|y, map(lambda x: CaselessLiteral(x), variables.keys())) + varnames.setParseAction(lambda x:map(lambda y:variables[y], x)) + else: + varnames=NoMatch() + # Same thing for functions. + if len(functions)>0: + funcnames = sreduce(lambda x,y:x|y, map(lambda x: CaselessLiteral(x), functions.keys())) + function = funcnames+lpar.suppress()+expr+rpar.suppress() + function.setParseAction(func_parse_action) + else: + function = NoMatch() + + atom = number | varnames | lpar+expr+rpar | function + factor << (atom + ZeroOrMore(exp+atom)).setParseAction(exp_parse_action) # 7^6 + paritem = factor + ZeroOrMore(Literal('||')+factor) # 5k || 4k + paritem=paritem.setParseAction(parallel) + term = paritem + ZeroOrMore((times|div)+paritem) # 7 * 5 / 4 - 3 + term = term.setParseAction(prod_parse_action) + expr << Optional((plus|minus)) + term + ZeroOrMore((plus|minus)+term) # -5 + 4 - 3 + expr=expr.setParseAction(sum_parse_action) + return (expr+stringEnd).parseString(string)[0] + +if __name__=='__main__': + variables={'R1':2.0, 'R3':4.0} + functions={'sin':math.sin, 'cos':math.cos} + print "X",evaluator(variables, functions, "10000||sin(7+5)-6k") + print "X",evaluator(variables, functions, "13") + print evaluator({'R1': 2.0, 'R3':4.0}, {}, "13") + # + print evaluator({'a': 2.2997471478310274, 'k': 9, 'm': 8, 'x': 0.66009498411213041}, {}, "5") + print evaluator({},{}, "-1") + print evaluator({},{}, "-(7+5)") + print evaluator({},{}, "-0.33") + print evaluator({},{}, "-.33") + print evaluator({},{}, "5+7 QWSEKO") diff --git a/courseware/content_parser.py b/courseware/content_parser.py index e577324568..af949f1f00 100644 --- a/courseware/content_parser.py +++ b/courseware/content_parser.py @@ -1,15 +1,15 @@ -try: +import json +import hashlib +import logging + +from lxml import etree + +try: # This lets us do __name__ == ='__main__' from django.conf import settings from auth.models import UserProfile except: settings = None -from lxml import etree - -import json -import hashlib -import logging - ''' This file will eventually form an abstraction layer between the course XML file and the rest of the system. diff --git a/courseware/models.py b/courseware/models.py index f334208597..acba401239 100644 --- a/courseware/models.py +++ b/courseware/models.py @@ -1,43 +1,6 @@ from django.db import models from django.contrib.auth.models import User -# class Organization(models.Model): -# # Tree structure implemented such that child node has left ID -# # greater than all parents, and right ID less than all parents -# left_tree_id = models.IntegerField(unique=True, db_index=True) -# right_tree_id = models.IntegerField(unique=True, db_index=True) -# # This is a duplicate, but we keep this to enforce unique name -# # constraint -# parent = models.ForeignKey('self', null=True, blank=True) -# name = models.CharField(max_length=200) -# ORG_TYPES= (('course','course'), -# ('chapter','chapter'), -# ('section','section'),) -# org_type = models.CharField(max_length=32, choices=ORG_TYPES) -# available = models.DateField(null=True, blank=True) -# due = models.DateField(null=True, blank=True) -# # JSON dictionary of metadata: -# # Time for a video, format of a section, etc. -# metadata = models.TextField(null=True, blank=True) - -# class Modules(models.Model): -# MOD_TYPES = (('hw','homework'), -# ('vid','video_clip'), -# ('lay','layout'), -# (),) -# module_type = models.CharField(max_length=100) -# left_tree_id = models.IntegerField(unique=True, db_index=True) -# right_tree_id = models.IntegerField(unique=True, db_index=True) - -# LAYOUT_TYPES = (('leaf','leaf'), -# ('tab','tab'), -# ('seq','sequential'), -# ('sim','simultaneous'),) -# layout_type = models.CharField(max_length=32, choices=LAYOUT_TYPES) -# data = models.TextField(null=True, blank=True) - -#class HomeworkProblems(models.Model): - class StudentModule(models.Model): # For a homework problem, contains a JSON # object consisting of state diff --git a/courseware/module_render.py b/courseware/module_render.py index b4e08ab17b..b31ddbc27a 100644 --- a/courseware/module_render.py +++ b/courseware/module_render.py @@ -1,43 +1,36 @@ -from django.http import HttpResponse -from django.template import Context, loader -from djangomako.shortcuts import render_to_response, render_to_string -import json, os, sys -from django.core.context_processors import csrf - -from django.db import connection -from django.template import Context -from django.contrib.auth.models import User -from auth.models import UserProfile -from django.shortcuts import redirect - import StringIO -import track.views - -from django.http import Http404 - +import json +import os +import sys +import sys import urllib +import uuid -import courseware.modules.capa_module -import courseware.modules.video_module -import courseware.modules.vertical_module -import courseware.modules.html_module -import courseware.modules.schematic_module -import courseware.modules.seq_module - -from models import StudentModule - -import urllib +from lxml import etree from django.conf import settings +from django.contrib.auth.models import User +from django.core.context_processors import csrf +from django.db import connection +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 mitxmako.shortcuts import render_to_response, render_to_string + +from auth.models import UserProfile +from models import StudentModule +import track.views import courseware.content_parser as content_parser -import sys -import logging - -from lxml import etree -import uuid - +import courseware.modules.capa_module +import courseware.modules.html_module +import courseware.modules.schematic_module +import courseware.modules.seq_module +import courseware.modules.vertical_module +import courseware.modules.video_module log = logging.getLogger("mitx.courseware") @@ -96,7 +89,7 @@ def modx_dispatch(request, module=None, dispatch=None, id=None): ajax_return=instance.handle_ajax(dispatch, request.POST) # Save the state back to the database s.state=instance.get_state() - if instance.get_score() != None: + if not instance.get_score(): s.grade=instance.get_score()['score'] s.save() # Return whatever the module wanted to return to the client/caller @@ -110,22 +103,14 @@ def render_x_module(user, request, xml_module, module_object_preload): module_id=xml_module.get('id') #module_class.id_attribute) or "" # Grab state from database - s = object_cache(module_object_preload, - user, - module_type, - module_id) - # s = StudentModule.objects.filter(student=request.user, - # module_id=module_id, - # module_type = module_type) - # if len(s) == 0: - # s=None - # else: - # s=s[0] + smod = object_cache(module_object_preload, + user, + module_type, + module_id) - if s == None: # If nothing in the database... + if not smod: # If nothing in the database... state=None else: - smod = s state = smod.state # Create a new instance @@ -135,10 +120,10 @@ def render_x_module(user, request, xml_module, module_object_preload): ajax_url=ajax_url, state=state, track_function = make_track_function(request), - render_function = lambda x: render_module(user, request, x, module_object_preload)) + render_function = lambda x: render_module(user, request, x, module_object_preload)) # If instance wasn't already in the database, create it - if s == None: + if not smod: smod=StudentModule(student=user, module_type = module_type, module_id=module_id, diff --git a/courseware/modules/capa_module.py b/courseware/modules/capa_module.py index 51cf6f823a..380117d9a0 100644 --- a/courseware/modules/capa_module.py +++ b/courseware/modules/capa_module.py @@ -1,21 +1,27 @@ -import random, numpy, math, scipy, sys, StringIO, os, struct, json -from x_module import XModule -import sys - -from courseware.capa.capa_problem import LoncapaProblem -from django.http import Http404 - +import StringIO +import datetime import dateutil import dateutil.parser -import datetime - -import courseware.content_parser as content_parser +import json +import math +import numpy +import os +import random +import scipy +import struct +import sys +import traceback from lxml import etree ## TODO: Abstract out from Django from django.conf import settings -from djangomako.shortcuts import render_to_response, render_to_string +from mitxmako.shortcuts import render_to_response, render_to_string +from django.http import Http404 + +from x_module import XModule +from courseware.capa.capa_problem import LoncapaProblem +import courseware.content_parser as content_parser class LoncapaModule(XModule): ''' Interface between capa_problem and x_module. Originally a hack @@ -231,6 +237,8 @@ class LoncapaModule(XModule): for key in get: answers['_'.join(key.split('_')[1:])]=get[key] + print "XXX", answers, get + event_info['answers']=answers # Too late. Cannot submit @@ -255,6 +263,7 @@ class LoncapaModule(XModule): correct_map = self.lcp.grade_answers(answers) except: self.lcp = LoncapaProblem(filename, id=lcp_id, state=old_state) + traceback.print_exc() print {'error':sys.exc_info(), 'answers':answers, 'seed':self.lcp.seed, diff --git a/courseware/modules/html_module.py b/courseware/modules/html_module.py index 0c5a0edae2..e72fea9dfd 100644 --- a/courseware/modules/html_module.py +++ b/courseware/modules/html_module.py @@ -1,11 +1,11 @@ -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 +from mitxmako.shortcuts import render_to_response, render_to_string + +from x_module import XModule +from lxml import etree class HtmlModule(XModule): id_attribute = 'filename' diff --git a/courseware/modules/schematic_module.py b/courseware/modules/schematic_module.py index a32de9b275..0312321b4d 100644 --- a/courseware/modules/schematic_module.py +++ b/courseware/modules/schematic_module.py @@ -1,10 +1,10 @@ -from x_module import XModule - import json ## TODO: Abstract out from Django from django.conf import settings -from djangomako.shortcuts import render_to_response, render_to_string +from mitxmako.shortcuts import render_to_response, render_to_string + +from x_module import XModule class SchematicModule(XModule): id_attribute = 'id' diff --git a/courseware/modules/seq_module.py b/courseware/modules/seq_module.py index 737f9ac2e9..02c8320ac8 100644 --- a/courseware/modules/seq_module.py +++ b/courseware/modules/seq_module.py @@ -1,12 +1,13 @@ -from x_module import XModule -from lxml import etree -from django.http import Http404 - import json +from lxml import etree + ## TODO: Abstract out from Django +from django.http import Http404 from django.conf import settings -from djangomako.shortcuts import render_to_response, render_to_string +from mitxmako.shortcuts import render_to_response, render_to_string + +from x_module import XModule class SequentialModule(XModule): ''' Layout module which lays out content in a temporal sequence diff --git a/courseware/modules/vertical_module.py b/courseware/modules/vertical_module.py index 73a7a6c521..6939c75cc9 100644 --- a/courseware/modules/vertical_module.py +++ b/courseware/modules/vertical_module.py @@ -1,11 +1,11 @@ -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 +from mitxmako.shortcuts import render_to_response, render_to_string + +from x_module import XModule +from lxml import etree class VerticalModule(XModule): id_attribute = 'id' diff --git a/courseware/modules/video_module.py b/courseware/modules/video_module.py index 354d988a2e..4225f36880 100644 --- a/courseware/modules/video_module.py +++ b/courseware/modules/video_module.py @@ -1,36 +1,40 @@ -from x_module import XModule -from lxml import etree - import json +import logging + +from lxml import etree ## TODO: Abstract out from Django from django.conf import settings -from djangomako.shortcuts import render_to_response, render_to_string +from mitxmako.shortcuts import render_to_response, render_to_string + +from x_module import XModule + +log = logging.getLogger("mitx.courseware.modules") class VideoModule(XModule): #id_attribute = 'youtube' video_time = 0 def handle_ajax(self, dispatch, get): - print "GET", get - print "DISPATCH", dispatch - if dispatch=='goto_position': + log.debug(u"GET {0}".format(get)) + log.debug(u"DISPATCH {0}".format(dispatch)) + if dispatch == 'goto_position': self.position = int(float(get['position'])) - print "NEW POSITION", self.position + log.debug(u"NEW POSITION {0}".format(self.position)) return json.dumps({'success':True}) raise Http404() def get_state(self): - print "STATE POSITION", self.position + log.debug(u"STATE POSITION {0}".format(self.position)) return json.dumps({ 'position':self.position }) 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" def video_list(self): - l=self.youtube.split(',') - l=[i.split(":") for i in l] + l = self.youtube.split(',') + l = [i.split(":") for i in l] return json.dumps(dict(l)) def get_html(self): @@ -39,24 +43,25 @@ class VideoModule(XModule): 'position':self.position}) 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 (e.g. student switching tabs). Common functions should be put in the main course .js files for now. ''' - print "INIT POSITION", self.position + log.debug(u"INIT POSITION {0}".format(self.position)) return render_to_string('video_init.js',{'streams':self.video_list(), 'id':self.item_id, 'position':self.position}) def get_destroy_js(self): - return "videoDestroy(\""+self.item_id+"\");" + return "videoDestroy(\"{0}\");".format(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) self.youtube = etree.XML(xml).get('youtube') self.position = 0 - if state!=None: + 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 + if 'position' in state: + self.position = int(float(state['position'])) + log.debug("POSITION IN STATE") + log.debug(u"LOAD POSITION {0}".format(self.position)) diff --git a/courseware/modules/x_module.py b/courseware/modules/x_module.py index efad4417e8..4b45c4c7fc 100644 --- a/courseware/modules/x_module.py +++ b/courseware/modules/x_module.py @@ -1,3 +1,5 @@ +import courseware.progress + def dummy_track(event_type, event): pass @@ -12,6 +14,9 @@ class XModule(object): ''' Tags in the courseware file guaranteed to correspond to the module ''' return [] + def get_completion(self): + return courseware.progress.completion() + def get_state(self): return "" diff --git a/courseware/progress.py b/courseware/progress.py index 6fe7cf3f2a..ad2cb725b4 100644 --- a/courseware/progress.py +++ b/courseware/progress.py @@ -1,6 +1,11 @@ class completion(object): - def __init__(self, d=None): - self.dict = dict() + def __init__(self, **d): + self.dict = dict({'duration_total':0, + 'duration_watched':0, + 'done':True, + 'questions_correct':0, + 'questions_incorrect':0, + 'questions_total':0}) if d: self.dict.update(d) @@ -11,9 +16,23 @@ class completion(object): self.dict[key] = value def __add__(self, other): - result = dict() - dict.update(self.dict) - dict.update(other.dict) + result = dict(self.dict) + for item in ['duration_total', + 'duration_watched', + 'done', + 'questions_correct', + 'questions_incorrect', + 'questions_total']: + result[item] = result[item]+other.dict[item] + return completion(**result) def __contains__(self, key): - pass + return key in dict + + def __repr__(self): + return repr(self.dict) + +if __name__ == '__main__': + dict1=completion(duration_total=5) + dict2=completion(duration_total=7) + print dict1+dict2 diff --git a/courseware/views.py b/courseware/views.py index ccfda0f678..ad06e6026c 100644 --- a/courseware/views.py +++ b/courseware/views.py @@ -12,16 +12,16 @@ from django.contrib.auth.models import User from django.http import HttpResponse, Http404 from django.shortcuts import redirect from django.template import Context, loader -from djangomako.shortcuts import render_to_response, render_to_string +from mitxmako.shortcuts import render_to_response, render_to_string from django.db import connection from lxml import etree from auth.models import UserProfile from models import StudentModule -from module_render import * # TODO: Clean up -from module_render import modx_dispatch +from module_render import render_module, modx_dispatch import courseware.content_parser as content_parser +import courseware.modules.capa_module log = logging.getLogger("mitx.courseware") @@ -36,10 +36,6 @@ def profile(request): if not request.user.is_authenticated(): return redirect('/') - log.info("Profile called") - logging.info("Now the root") - logging.getLogger("tracking").info("this should be unformatted") - dom=content_parser.course_file(request.user) hw=[] course = dom.xpath('//course/@name')[0] @@ -145,6 +141,7 @@ def index(request, course="6.002 Spring 2012", chapter="Using the System", secti module_object_preload = list(StudentModule.objects.filter(student=user, module_id__in=module_ids)) + module=render_module(user, request, module, module_object_preload) diff --git a/mitxmako/README b/mitxmako/README new file mode 100644 index 0000000000..ab04df2cf7 --- /dev/null +++ b/mitxmako/README @@ -0,0 +1,15 @@ +================================================================================ +django-mako +================================================================================ +This module provides a drop in replacement of Django templates for Mako +Templates. + +Django: http://www.djangoproject.com/ +Mako: http://www.makotemplates.org/ + +================================================================================ +How to install? +================================================================================ + + $ sudo python setup.py install + diff --git a/mitxmako/__init__.py b/mitxmako/__init__.py new file mode 100644 index 0000000000..aa8a48bfe7 --- /dev/null +++ b/mitxmako/__init__.py @@ -0,0 +1,16 @@ +# Copyright (c) 2008 Mikeal Rogers +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +lookup = None + diff --git a/mitxmako/middleware.py b/mitxmako/middleware.py new file mode 100644 index 0000000000..481d1db672 --- /dev/null +++ b/mitxmako/middleware.py @@ -0,0 +1,49 @@ +# Copyright (c) 2008 Mikeal Rogers +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from mako.lookup import TemplateLookup +import tempfile +from django.template import RequestContext + +requestcontext = None +lookup = None + +class MakoMiddleware(object): + def __init__(self): + """Setup mako variables and lookup object""" + from django.conf import settings + # Set all mako variables based on django settings + global template_dirs, output_encoding, module_directory, encoding_errors + directories = getattr(settings, 'MAKO_TEMPLATE_DIRS', settings.TEMPLATE_DIRS) + + module_directory = getattr(settings, 'MAKO_MODULE_DIR', None) + if module_directory is None: + module_directory = tempfile.mkdtemp() + + output_encoding = getattr(settings, 'MAKO_OUTPUT_ENCODING', 'utf-8') + encoding_errors = getattr(settings, 'MAKO_ENCODING_ERRORS', 'replace') + + global lookup + lookup = TemplateLookup(directories=directories, + module_directory=module_directory, + output_encoding=output_encoding, + encoding_errors=encoding_errors, + ) + import mitxmako + mitxmako.lookup = lookup + + def process_request (self, request): + global requestcontext + requestcontext = RequestContext(request) +# print requestcontext diff --git a/mitxmako/shortcuts.py b/mitxmako/shortcuts.py new file mode 100644 index 0000000000..261cee5b41 --- /dev/null +++ b/mitxmako/shortcuts.py @@ -0,0 +1,42 @@ +# Copyright (c) 2008 Mikeal Rogers +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from django.template import Context +from django.http import HttpResponse + +import mitxmako.middleware as middleware +from django.conf import settings + +from mitxmako.middleware import requestcontext + +def render_to_string(template_name, dictionary, context_instance=None): + context_instance = context_instance or Context(dictionary) + # add dictionary to context_instance + context_instance.update(dictionary or {}) + # collapse context_instance to a single dictionary for mako + context_dictionary = {} + context_instance['settings'] = settings + context_instance['request_context'] = requestcontext + for d in context_instance: + context_dictionary.update(d) + # fetch and render template + template = middleware.lookup.get_template(template_name) + return template.render(**context_dictionary) + +def render_to_response(template_name, dictionary, context_instance=None, **kwargs): + """ + Returns a HttpResponse whose content is filled with the result of calling + lookup.get_template(args[0]).render with the passed arguments. + """ + return HttpResponse(render_to_string(template_name, dictionary, context_instance), **kwargs) diff --git a/mitxmako/template.py b/mitxmako/template.py new file mode 100644 index 0000000000..9e5897ef25 --- /dev/null +++ b/mitxmako/template.py @@ -0,0 +1,28 @@ +# Copyright (c) 2008 Mikeal Rogers +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from mako.template import Template as MakoTemplate + +import middleware + +django_variables = ['lookup', 'template_dirs', 'output_encoding', + 'module_directory', 'encoding_errors',] + +class Template(MakoTemplate): + def __init__(self, *args, **kwargs): + """Overrides base __init__ to provide django variable overrides""" + if not kwargs.get('no_django', False): + overrides = dict([(k, getattr(middleware, k, None),) for k in django_variables]) + kwargs.update(overrides) + super(Template, self).__init__(*args, **kwargs) diff --git a/perfstats/middleware.py b/perfstats/middleware.py index ebb8e8142c..1308dd650a 100644 --- a/perfstats/middleware.py +++ b/perfstats/middleware.py @@ -1,7 +1,11 @@ -import views, json, tempfile, time +import json +import tempfile +import time + from django.conf import settings from django.db import connection +import views class ProfileMiddleware: def process_request (self, request): diff --git a/perfstats/views.py b/perfstats/views.py index fef5100a4b..7d0695b9fe 100644 --- a/perfstats/views.py +++ b/perfstats/views.py @@ -1,5 +1,6 @@ # Create your views here. import middleware + from django.http import HttpResponse def end_profile(request): diff --git a/settings_new_askbot.py b/settings_new_askbot.py index d54af20813..9786bd1c91 100644 --- a/settings_new_askbot.py +++ b/settings_new_askbot.py @@ -3,12 +3,16 @@ import sys import djcelery +LIB_URL = '/static/lib/' +LIB_URL = 'http://mitxstatic.s3-website-us-east-1.amazonaws.com/js/' +BOOK_URL = '/static/book/' +BOOK_URL = 'http://mitxstatic.s3-website-us-east-1.amazonaws.com/book_images/' + # Our parent dir (mitx_all) is the BASE_DIR BASE_DIR = os.path.abspath(os.path.join(__file__, "..", "..")) COURSEWARE_ENABLED = True ASKBOT_ENABLED = True - CSRF_COOKIE_DOMAIN = '127.0.0.1' # Defaults to be overridden @@ -34,7 +38,7 @@ DEBUG = True TEMPLATE_DEBUG = DEBUG ADMINS = ( - ('Piotr Mitros', 'pmitros@csail.mit.edu'), + ('Piotr Mitros', 'staff@csail.mit.edu'), ) MANAGERS = ADMINS @@ -56,87 +60,6 @@ USE_I18N = True # calendars according to the current locale USE_L10N = True -# 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': True, - 'formatters' : { - 'standard' : { - 'format' : '%(levelname)s %(asctime)s PID:%(process)d %(name)s %(filename)s:%(lineno)d %(message)s', - }, - 'raw' : { - 'format' : '%(message)s', - } - }, - 'handlers' : { - 'console' : { - 'level' : 'DEBUG' if DEBUG else 'INFO', - 'class' : 'logging.StreamHandler', - 'formatter' : 'standard', - 'stream' : sys.stdout, - }, - 'console_err' : { - 'level' : 'ERROR', - 'class' : 'logging.StreamHandler', - 'formatter' : 'standard', - 'stream' : sys.stderr, - }, - # 'app' : { - # 'level' : 'INFO', - # 'class' : 'logging.handlers.TimedRotatingFileHandler', - # 'formatter' : 'standard', - # 'filename' : '/tmp/mitx.log', # temporary location for proof of concept - # 'when' : 'midnight', - # 'utc' : True, - # 'encoding' : 'utf-8', - # }, - - # We should actually use this for tracking: - # http://pypi.python.org/pypi/ConcurrentLogHandler/0.8.2 - 'tracking' : { - 'level' : 'INFO', - 'class' : 'logging.handlers.TimedRotatingFileHandler', - 'formatter' : 'raw', - 'filename' : BASE_DIR + '/track_dir/tracking.log', - 'when' : 'midnight', - 'utc' : True, - 'encoding' : 'utf-8', - }, - 'mail_admins' : { - 'level': 'ERROR', - 'class': 'django.utils.log.AdminEmailHandler', - }, - }, - 'loggers' : { - 'django.request': { - 'handlers': ['mail_admins', 'console', 'console_err'], - 'level': 'INFO', - 'propagate': True, - }, - 'tracking' : { - 'handlers' : ['tracking'], - 'level' : 'DEBUG', - 'propagate' : False, - }, - 'root' : { - 'handlers' : ['console', 'console_err'], - 'level' : 'DEBUG', - 'propagate' : False - }, - 'mitx' : { - 'handlers' : ['console', 'console_err'], - 'level' : 'DEBUG', - 'propagate' : False - }, - } -} - - - STATIC_URL = '/static/' # URL prefix for admin static files -- CSS, JavaScript and images. @@ -166,7 +89,7 @@ MIDDLEWARE_CLASSES = ( 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'track.middleware.TrackMiddleware', - 'djangomako.middleware.MakoMiddleware', + 'mitxmako.middleware.MakoMiddleware', #'debug_toolbar.middleware.DebugToolbarMiddleware', ) @@ -188,6 +111,7 @@ INSTALLED_APPS = ( 'track', 'circuit', 'perfstats', + 'util', # Uncomment the next line to enable the admin: # 'django.contrib.admin', # Uncomment the next line to enable admin documentation: @@ -201,9 +125,94 @@ TRACK_MAX_EVENT = 1000 # Maximum length of log file before starting a new one. MAXLOG = 500 +LOG_DIR = "/tmp/" + # Make sure we execute correctly regardless of where we're called from execfile(os.path.join(BASE_DIR, "settings.py")) +# 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': True, + 'formatters' : { + 'standard' : { + 'format' : '%(asctime)s %(levelname)s %(process)d [%(name)s] %(filename)s:%(lineno)d - %(message)s', + }, + 'raw' : { + 'format' : '%(message)s', + } + }, + 'handlers' : { + 'console' : { + 'level' : 'DEBUG' if DEBUG else 'INFO', + 'class' : 'logging.StreamHandler', + 'formatter' : 'standard', + 'stream' : sys.stdout, + }, + 'console_err' : { + 'level' : 'ERROR', + 'class' : 'logging.StreamHandler', + 'formatter' : 'standard', + 'stream' : sys.stderr, + }, + 'app' : { + 'level' : 'DEBUG' if DEBUG else 'INFO', + 'class' : 'logging.handlers.WatchedFileHandler', + 'formatter' : 'standard', + 'filename' : LOG_DIR + '/mitx.log', # temporary location for proof of concept + 'encoding' : 'utf-8', + }, + 'app_err' : { + 'level' : 'WARNING', + 'class' : 'logging.handlers.WatchedFileHandler', + 'formatter' : 'standard', + 'filename' : LOG_DIR + '/mitx.err.log', # temporary location for proof of concept + 'encoding' : 'utf-8', + }, + # We should actually use this for tracking: + # http://pypi.python.org/pypi/ConcurrentLogHandler/0.8.2 + 'tracking' : { + 'level' : 'INFO', + 'class' : 'logging.handlers.WatchedFileHandler', + 'formatter' : 'raw', + 'filename' : LOG_DIR + '/tracking.log', + 'encoding' : 'utf-8', + }, + 'mail_admins' : { + 'level': 'ERROR', + 'class': 'django.utils.log.AdminEmailHandler', + }, + }, + 'loggers' : { + 'django' : { + 'handlers' : ['console', 'mail_admins', 'app_err'], + 'propagate' : True, + 'level' : 'INFO' + }, + 'tracking' : { + 'handlers' : ['console', 'tracking'], + 'level' : 'DEBUG', + 'propagate' : False, + }, + 'root' : { + 'handlers' : ['console', 'app', 'app_err'], + 'level' : 'DEBUG', + 'propagate' : False + }, + 'mitx' : { + 'handlers' : ['console', 'app', 'app_err'], + 'level' : 'DEBUG', + 'propagate' : False + }, + } +} + + + if PERFSTATS : MIDDLEWARE_CLASSES = ( 'perfstats.middleware.ProfileMiddleware',) + MIDDLEWARE_CLASSES @@ -252,6 +261,7 @@ 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 + ( + 'util.middleware.ExceptionLoggingMiddleware', 'askbot.middleware.anon_user.ConnectToSessionMessagesMiddleware', 'askbot.middleware.forum_mode.ForumModeMiddleware', 'askbot.middleware.cancel.CancelActionMiddleware', @@ -312,3 +322,4 @@ BROKER_TRANSPORT = "djkombu.transport.DatabaseTransport" CELERY_ALWAYS_EAGER = True djcelery.setup_loader() + diff --git a/settings_old_askbot.py b/settings_old_askbot.py index 8ab2a98df8..b711440d65 100644 --- a/settings_old_askbot.py +++ b/settings_old_askbot.py @@ -26,7 +26,7 @@ DEBUG = True TEMPLATE_DEBUG = DEBUG ADMINS = ( - ('Piotr Mitros', 'pmitros@csail.mit.edu'), + ('Piotr Mitros', 'staff@csail.mit.edu'), ) MANAGERS = ADMINS @@ -81,7 +81,7 @@ MIDDLEWARE_CLASSES = ( 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'track.middleware.TrackMiddleware', - 'djangomako.middleware.MakoMiddleware', + 'mitxmako.middleware.MakoMiddleware', #'debug_toolbar.middleware.DebugToolbarMiddleware', ) diff --git a/simplewiki/__init__.py b/simplewiki/__init__.py index 071ec2c38f..8c4ebf2d51 100644 --- a/simplewiki/__init__.py +++ b/simplewiki/__init__.py @@ -1,6 +1,7 @@ # Source: django-simplewiki. GPL license. -import sys, os +import os +import sys # allow mdx_* parsers to be just dropped in the simplewiki folder module_path = os.path.abspath(os.path.dirname(__file__)) diff --git a/simplewiki/admin.py b/simplewiki/admin.py index 31e0856b75..b53ace1a7a 100644 --- a/simplewiki/admin.py +++ b/simplewiki/admin.py @@ -1,8 +1,9 @@ # Source: django-simplewiki. GPL license. -from django.contrib import admin from django import forms +from django.contrib import admin from django.utils.translation import ugettext as _ + from models import Article, Revision, Permission, ArticleAttachment class RevisionInline(admin.TabularInline): diff --git a/simplewiki/mdx_circuit.py b/simplewiki/mdx_circuit.py index efac721b7c..465e7d9a3a 100755 --- a/simplewiki/mdx_circuit.py +++ b/simplewiki/mdx_circuit.py @@ -8,7 +8,7 @@ circuit:name becomes the circuit. import simplewiki.settings as settings -from djangomako.shortcuts import render_to_response, render_to_string +from mitxmako.shortcuts import render_to_response, render_to_string import markdown try: diff --git a/simplewiki/models.py b/simplewiki/models.py index 71add3c0fc..ed6366e801 100644 --- a/simplewiki/models.py +++ b/simplewiki/models.py @@ -1,12 +1,14 @@ -from django.utils.translation import ugettext_lazy as _ -from django.db import models -from django.db.models import signals -from django.contrib.auth.models import User -from markdown import markdown -from django import forms -from django.core.urlresolvers import reverse import difflib import os + +from django import forms +from django.contrib.auth.models import User +from django.core.urlresolvers import reverse +from django.db import models +from django.db.models import signals +from django.utils.translation import ugettext_lazy as _ +from markdown import markdown + from settings import * class ShouldHaveExactlyOneRootSlug(Exception): diff --git a/simplewiki/templatetags/simplewiki_utils.py b/simplewiki/templatetags/simplewiki_utils.py index 5b8eccf910..1534a7b401 100644 --- a/simplewiki/templatetags/simplewiki_utils.py +++ b/simplewiki/templatetags/simplewiki_utils.py @@ -1,9 +1,10 @@ from django import template -from django.template.defaultfilters import stringfilter -from simplewiki.settings import * from django.conf import settings +from django.template.defaultfilters import stringfilter from django.utils.http import urlquote as django_urlquote +from simplewiki.settings import * + register = template.Library() @register.filter() @@ -14,4 +15,4 @@ def prepend_media_url(value): @register.filter() def urlquote(value): """Prepend user defined media root to url""" - return django_urlquote(value) \ No newline at end of file + return django_urlquote(value) diff --git a/simplewiki/urls.py b/simplewiki/urls.py index 05a631f0be..0a95fc305a 100644 --- a/simplewiki/urls.py +++ b/simplewiki/urls.py @@ -3,6 +3,7 @@ from django.conf.urls.defaults import * urlpatterns = patterns('', url(r'^$', 'simplewiki.views.root_redirect', name='wiki_root'), url(r'^view(/[a-zA-Z\d/_-]*)/?$', 'simplewiki.views.view', name='wiki_view'), + url(r'^view_revision/([0-9]*)(/[a-zA-Z\d/_-]*)/?$', 'simplewiki.views.view_revision', name='wiki_view_revision'), url(r'^edit(/[a-zA-Z\d/_-]*)/?$', 'simplewiki.views.edit', name='wiki_edit'), url(r'^create(/[a-zA-Z\d/_-]*)/?$', 'simplewiki.views.create', name='wiki_create'), url(r'^history(/[a-zA-Z\d/_-]*)/([0-9]*)/?$', 'simplewiki.views.history', name='wiki_history'), diff --git a/simplewiki/views.py b/simplewiki/views.py index a49d135aee..856a45d1c0 100644 --- a/simplewiki/views.py +++ b/simplewiki/views.py @@ -1,27 +1,26 @@ # -*- coding: utf-8 -*- import types -from django.core.urlresolvers import get_callable -from django.http import Http404, HttpResponse, HttpResponseRedirect, HttpResponseServerError, HttpResponseForbidden, HttpResponseNotAllowed -from django.utils import simplejson -from djangomako.shortcuts import render_to_response, render_to_string -from django.shortcuts import get_object_or_404 -from django.template import RequestContext, Context, loader -from django.utils.translation import ugettext_lazy as _ -from django.core.urlresolvers import reverse -from django.contrib.auth.decorators import login_required -from django.db.models import Q + from django.conf import settings -from django.shortcuts import redirect +from django.contrib.auth.decorators import login_required from django.core.context_processors import csrf - -from django.template import Context +from django.core.urlresolvers import get_callable +from django.core.urlresolvers import reverse +from django.db.models import Q +from django.http import Http404, HttpResponse, HttpResponseRedirect, HttpResponseServerError, HttpResponseForbidden, HttpResponseNotAllowed from django.http import HttpResponse - -import djangomako.middleware -from mako.template import Template +from django.shortcuts import get_object_or_404 +from django.shortcuts import redirect +from django.template import Context +from django.template import RequestContext, Context, loader +from django.utils import simplejson +from django.utils.translation import ugettext_lazy as _ +from mitxmako.shortcuts import render_to_response, render_to_string from mako.lookup import TemplateLookup +from mako.template import Template +import mitxmako.middleware -from models import * +from models import * # TODO: Clean up from settings import * def view(request, wiki_url): @@ -36,12 +35,44 @@ def view(request, wiki_url): if perm_err: return perm_err d = {'wiki_article': article, + 'wiki_article_revision':article.current_revision, 'wiki_write': article.can_write_l(request.user), 'wiki_attachments_write': article.can_attach(request.user), 'wiki_current_revision_deleted' : not (article.current_revision.deleted == 0), } d.update(csrf(request)) return render_to_response('simplewiki_view.html', d) + +def view_revision(request, revision_number, wiki_url, revision=None): + if not request.user.is_authenticated(): + return redirect('/') + + (article, path, err) = fetch_from_url(request, wiki_url) + if err: + return err + + try: + revision = Revision.objects.get(counter=int(revision_number), article=article) + except: + d = {'wiki_article': article, + 'wiki_err_norevision': revision_number,} + d.update(csrf(request)) + return render_to_response('simplewiki_error.html', d) + + + perm_err = check_permissions(request, article, check_read=True, check_deleted=True, revision=revision) + if perm_err: + return perm_err + + d = {'wiki_article': article, + 'wiki_article_revision':revision, + 'wiki_write': article.can_write_l(request.user), + 'wiki_attachments_write': article.can_attach(request.user), + 'wiki_current_revision_deleted' : not (revision.deleted == 0), + } + d.update(csrf(request)) + return render_to_response('simplewiki_view.html', d) + def root_redirect(request): if not request.user.is_authenticated(): @@ -206,12 +237,18 @@ def history(request, wiki_url, page=1): perm_err = check_permissions(request, article, check_write=True, check_locked=True) if perm_err: return perm_err + + redirectURL = reverse('wiki_view', args=(article.get_url(),)) try: r = int(request.POST['revision']) revision = Revision.objects.get(id=r) if request.POST.__contains__('change'): article.current_revision = revision article.save() + elif request.POST.__contains__('view'): + redirectURL = reverse('wiki_view_revision', args=(revision.counter, article.get_url(),)) + + #The rese of these are admin functions elif request.POST.__contains__('delete') and request.user.is_superuser: if (revision.deleted == 0): revision.adminSetDeleted(2) @@ -228,7 +265,7 @@ def history(request, wiki_url, page=1): except: pass finally: - return HttpResponseRedirect(reverse('wiki_view', args=(article.get_url(),))) + return HttpResponseRedirect(redirectURL) # # # @@ -433,14 +470,16 @@ def fetch_from_url(request, url): return (article, path, err) -def check_permissions(request, article, check_read=False, check_write=False, check_locked=False, check_deleted=False): +def check_permissions(request, article, check_read=False, check_write=False, check_locked=False, check_deleted=False, revision = None): read_err = check_read and not article.can_read(request.user) write_err = check_write and not article.can_write(request.user) locked_err = check_locked and article.locked - deleted_err = check_deleted and not (article.current_revision.deleted == 0) + if revision == None: + revision = article.current_revision + deleted_err = check_deleted and not (revision.deleted == 0) if (request.user.is_superuser): deleted_err = False locked_err = False diff --git a/simplewiki/views_attachments.py b/simplewiki/views_attachments.py index 47eb09a0b2..205f836ab9 100644 --- a/simplewiki/views_attachments.py +++ b/simplewiki/views_attachments.py @@ -1,14 +1,15 @@ +import os + +from django.contrib.auth.decorators import login_required +from django.core.servers.basehttp import FileWrapper +from django.db.models.fields.files import FieldFile from django.http import HttpResponse, HttpResponseRedirect, HttpResponseForbidden, Http404 from django.template import loader, Context -from django.db.models.fields.files import FieldFile -from django.core.servers.basehttp import FileWrapper -from django.contrib.auth.decorators import login_required -from settings import * +from settings import * # TODO: Clean up from models import Article, ArticleAttachment, get_attachment_filepath from views import not_found, check_permissions, get_url_path, fetch_from_url -import os from simplewiki.settings import WIKI_ALLOW_ANON_ATTACHMENTS diff --git a/static_template_view/views.py b/static_template_view/views.py index 5579e20c86..734434b43a 100644 --- a/static_template_view/views.py +++ b/static_template_view/views.py @@ -3,7 +3,7 @@ # List of valid templates is explicitly managed for (short-term) # security reasons. -from djangomako.shortcuts import render_to_response, render_to_string +from mitxmako.shortcuts import render_to_response, render_to_string from django.shortcuts import redirect from django.core.context_processors import csrf diff --git a/staticbook/views.py b/staticbook/views.py index dbfab3852c..f7ad8fab63 100644 --- a/staticbook/views.py +++ b/staticbook/views.py @@ -1,11 +1,15 @@ # Create your views here. -from djangomako.shortcuts import render_to_response, render_to_string -from django.shortcuts import redirect import os + from django.conf import settings from django.http import Http404 +from django.shortcuts import redirect +from mitxmako.shortcuts import render_to_response, render_to_string -def index(request, page=1): +def index(request, page=0): if not request.user.is_authenticated(): return redirect('/') return render_to_response('staticbook.html',{'page':int(page)}) + +def index_shifted(request, page): + return index(request, int(page)+24) diff --git a/track/middleware.py b/track/middleware.py index 866accbeb5..6905ae86f3 100644 --- a/track/middleware.py +++ b/track/middleware.py @@ -1,10 +1,15 @@ -import views, json +import json + +from django.conf import settings + +import views class TrackMiddleware: - def process_request (self, request): + def process_request(self, request): try: - # We're already logging events - if request.META['PATH_INFO'] == '/event': + # We're already logging events, and we don't want to capture user + # names/passwords. + if request.META['PATH_INFO'] in ['/event', '/login']: return event = { 'GET' : dict(request.GET), diff --git a/track/views.py b/track/views.py index 2a368b3188..a06baa6212 100644 --- a/track/views.py +++ b/track/views.py @@ -1,47 +1,17 @@ +import json +import logging +import os + # Create your views here. from django.http import HttpResponse from django.http import Http404 from django.conf import settings -import json, os, stat -import tempfile - -if settings.TRACK_DIR != None: - directory = tempfile.mkdtemp(prefix = settings.TRACK_DIR) -else: - directory = None - -logfile = None -file_index = 0 -log_index = 0 -filename = None - -def make_file(): - global logfile, log_index, file_index, filename - if logfile != None: - logfile.close() - os.chmod(filename, stat.S_IRUSR | stat.S_IWUSR | \ - stat.S_IRGRP | stat.S_IWGRP | \ - stat.S_IROTH ) - filename = directory+"/%05i"%(file_index)+".trklog" - logfile = open(filename, "w") - file_index = file_index + 1 - log_index = 0 +log = logging.getLogger("tracking") def log_event(event): - global logfile, log_index event_str = json.dumps(event) - if settings.TRACK_DIR == None: -# print event - return - - if logfile == None or log_index >= settings.MAXLOG: - make_file() - - logfile.write(event_str[:settings.TRACK_MAX_EVENT]+'\n') - if settings.DEBUG_TRACK_LOG: - print event_str - log_index = log_index + 1 + log.info(event_str[:settings.TRACK_MAX_EVENT]) def user_track(request): try: # TODO: Do the same for many of the optional META parameters @@ -49,15 +19,25 @@ def user_track(request): except: username = "anonymous" + try: + scookie = request.META['HTTP_COOKIE'] + except: + scookie = "" + + try: + agent = request.META['HTTP_USER_AGENT'] + except: + agent = '' + # TODO: Move a bunch of this into log_event event = { "username" : username, - "session" : request.META['HTTP_COOKIE'], + "session" : scookie, "ip" : request.META['REMOTE_ADDR'], "event_source" : "browser", "event_type" : request.GET['event_type'], "event" : request.GET['event'], - "agent" : request.META['HTTP_USER_AGENT'], + "agent" : agent, "page" : request.GET['page'], } log_event(event) @@ -69,13 +49,18 @@ def server_track(request, event_type, event, page=None): except: username = "anonymous" + try: + agent = request.META['HTTP_USER_AGENT'] + except: + agent = '' + event = { "username" : username, "ip" : request.META['REMOTE_ADDR'], "event_source" : "server", "event_type" : event_type, "event" : event, - "agent" : request.META['HTTP_USER_AGENT'], + "agent" : agent, "page" : page, } log_event(event) diff --git a/urls.py b/urls.py index 6d5cfeb561..5ebda7bd4e 100644 --- a/urls.py +++ b/urls.py @@ -1,8 +1,7 @@ -from django.conf.urls.defaults import patterns, include, url -import django.contrib.auth.views from django.conf import settings +from django.conf.urls.defaults import patterns, include, url from django.contrib import admin -import perfstats +import django.contrib.auth.views # Uncomment the next two lines to enable the admin: # from django.contrib import admin @@ -19,7 +18,7 @@ urlpatterns = ('', url(r'^activate/(?P[^/]*)$', 'auth.views.activate_account'), url(r'^$', 'auth.views.index'), url(r'^password_reset/$', 'django.contrib.auth.views.password_reset', - dict(from_email='6002-admin@mit.edu'),name='auth_password_reset'), + dict(from_email='registration@mitx.mit.edu'),name='auth_password_reset'), url(r'^password_change/$',django.contrib.auth.views.password_change,name='auth_password_change'), url(r'^password_change_done/$',django.contrib.auth.views.password_change_done,name='auth_password_change_done'), url(r'^password_reset_confirm/(?P[0-9A-Za-z]+)-(?P.+)/$',django.contrib.auth.views.password_reset_confirm, @@ -29,14 +28,14 @@ urlpatterns = ('', url(r'^password_reset_done/$',django.contrib.auth.views.password_reset_done, name='auth_password_reset_done'), url(r'^send_feedback$', 'util.views.send_feedback'), - url(r'^courseware/$', 'courseware.views.index'), ) if settings.PERFSTATS: urlpatterns=urlpatterns + (url(r'^reprofile$','perfstats.views.end_profile'),) if settings.COURSEWARE_ENABLED: - urlpatterns=urlpatterns + (url(r'^wiki/', include('simplewiki.urls')), + urlpatterns=urlpatterns + ( url(r'^courseware/$', 'courseware.views.index'), +url(r'^wiki/', include('simplewiki.urls')), url(r'^courseware/(?P[^/]*)/(?P[^/]*)/(?P
[^/]*)/$', 'courseware.views.index'), url(r'^courseware/(?P[^/]*)/(?P[^/]*)/$', 'courseware.views.index'), url(r'^courseware/(?P[^/]*)/$', 'courseware.views.index'), @@ -45,6 +44,7 @@ if settings.COURSEWARE_ENABLED: url(r'^change_setting$', 'auth.views.change_setting'), url(r'^s/(?P