diff --git a/auth/views.py b/auth/views.py index 1b6069b593..fa8c56933f 100644 --- a/auth/views.py +++ b/auth/views.py @@ -74,8 +74,10 @@ def login_user(request, error=""): 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})) + 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/courseware/capa/capa_problem.py b/courseware/capa/capa_problem.py index 46ad5e861d..a6021af3bc 100644 --- a/courseware/capa/capa_problem.py +++ b/courseware/capa/capa_problem.py @@ -133,7 +133,7 @@ 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()) + answer_map[entry.get('id')] = contextualize_text(answer, self.context) return answer_map diff --git a/courseware/capa/responsetypes.py b/courseware/capa/responsetypes.py index 3e3cefd8ab..d925f31b35 100644 --- a/courseware/capa/responsetypes.py +++ b/courseware/capa/responsetypes.py @@ -3,12 +3,16 @@ from util import contextualize_text from calc import evaluator import random, math from django.conf import settings +import eia +import calc # 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..c6a4d4591a --- /dev/null +++ b/courseware/capa/unit.py @@ -0,0 +1,130 @@ +import math +from numpy import eye, array +import operator +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/modules/video_module.py b/courseware/modules/video_module.py index 354d988a2e..93650cb6d3 100644 --- a/courseware/modules/video_module.py +++ b/courseware/modules/video_module.py @@ -1,36 +1,39 @@ -from x_module import XModule -from lxml import etree - +import logging import json ## TODO: Abstract out from Django from django.conf import settings from djangomako.shortcuts import render_to_response, render_to_string +from lxml import etree + +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 +42,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/settings_new_askbot.py b/settings_new_askbot.py index 2ef4b5f893..14ede01c85 100644 --- a/settings_new_askbot.py +++ b/settings_new_askbot.py @@ -155,7 +155,7 @@ LOGGING = { 'stream' : sys.stderr, }, 'app' : { - 'level' : 'INFO', + 'level' : 'DEBUG' if DEBUG else 'INFO', 'class' : 'logging.handlers.TimedRotatingFileHandler', 'formatter' : 'standard', 'filename' : LOG_DIR + '/mitx.log', # temporary location for proof of concept @@ -262,6 +262,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', diff --git a/staticbook/views.py b/staticbook/views.py index dbfab3852c..ad1314a71c 100644 --- a/staticbook/views.py +++ b/staticbook/views.py @@ -5,7 +5,7 @@ import os from django.conf import settings from django.http import Http404 -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)}) diff --git a/track/middleware.py b/track/middleware.py index 407f64b992..6905ae86f3 100644 --- a/track/middleware.py +++ b/track/middleware.py @@ -5,10 +5,11 @@ 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/urls.py b/urls.py index 6d5cfeb561..f24b2088f2 100644 --- a/urls.py +++ b/urls.py @@ -19,7 +19,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, diff --git a/util/middleware.py b/util/middleware.py new file mode 100644 index 0000000000..3bda2c7873 --- /dev/null +++ b/util/middleware.py @@ -0,0 +1,13 @@ +import logging + +from django.http import HttpResponse + +log = logging.getLogger("mitx") + +class ExceptionLoggingMiddleware(object): + """Just here to log unchecked exceptions that go all the way up the Django + stack""" + + def process_exception(self, request, exception): + log.exception(exception) + return HttpResponse("Server Error - Please try again later.")