From e5ca9618ea5ae9f2fc11ea5a8c9f479b1430d84c Mon Sep 17 00:00:00 2001 From: David Ormsbee Date: Thu, 26 Jan 2012 19:05:52 -0500 Subject: [PATCH 1/7] remove /login from tracking logs so we don't capture passwords --- auth/views.py | 2 ++ track/middleware.py | 7 ++++--- 2 files changed, 6 insertions(+), 3 deletions(-) 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/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), From efdaa04742bb01ce6c04d494b46b45c309069e21 Mon Sep 17 00:00:00 2001 From: David Ormsbee Date: Fri, 27 Jan 2012 10:57:36 -0500 Subject: [PATCH 2/7] convert more prints to logging --- courseware/modules/video_module.py | 40 ++++++++++++++++-------------- settings_new_askbot.py | 2 +- 2 files changed, 23 insertions(+), 19 deletions(-) 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..39c9602eac 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 From 84794a230da7039e030936d522efc917a4198407 Mon Sep 17 00:00:00 2001 From: David Ormsbee Date: Fri, 27 Jan 2012 19:07:46 -0500 Subject: [PATCH 3/7] make sure exceptions that go all the way up the django stack are logged in our error logs when DEBUG=False --- settings_new_askbot.py | 1 + util/middleware.py | 13 +++++++++++++ 2 files changed, 14 insertions(+) create mode 100644 util/middleware.py diff --git a/settings_new_askbot.py b/settings_new_askbot.py index 39c9602eac..14ede01c85 100644 --- a/settings_new_askbot.py +++ b/settings_new_askbot.py @@ -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/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.") From 6c1a7ce66e86ed3d2e12e864b0159fde3d02b7b4 Mon Sep 17 00:00:00 2001 From: fischerl Date: Fri, 27 Jan 2012 19:16:24 -0500 Subject: [PATCH 4/7] default to cover of textbook --- staticbook/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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)}) From 1268530f7af4e0a95c78f3d115d7c375e12c216c Mon Sep 17 00:00:00 2001 From: Piotr Mitros Date: Fri, 27 Jan 2012 22:09:47 -0500 Subject: [PATCH 5/7] Preparing for stage --- courseware/capa/unit.py | 130 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 courseware/capa/unit.py 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") From 04a7be0ae7a1a4ad2512a89399f0f3d058f2e34b Mon Sep 17 00:00:00 2001 From: Piotr Mitros Date: Fri, 27 Jan 2012 22:37:34 -0500 Subject: [PATCH 6/7] Fixed e-mail in URLs.py, fixed 2 gjs bugs --- courseware/capa/capa_problem.py | 2 +- courseware/capa/responsetypes.py | 4 +++- urls.py | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) 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..2fbc16ed47 100644 --- a/courseware/capa/responsetypes.py +++ b/courseware/capa/responsetypes.py @@ -8,7 +8,9 @@ from django.conf import settings 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/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, From cbd8a96431d04a6c72c5e6c7131699c78a905169 Mon Sep 17 00:00:00 2001 From: Piotr Mitros Date: Fri, 27 Jan 2012 22:39:56 -0500 Subject: [PATCH 7/7] Bug in last commit --- courseware/capa/responsetypes.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/courseware/capa/responsetypes.py b/courseware/capa/responsetypes.py index 2fbc16ed47..d925f31b35 100644 --- a/courseware/capa/responsetypes.py +++ b/courseware/capa/responsetypes.py @@ -3,6 +3,8 @@ 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,