From 6304feefd649cd7b52d1830dd9a33551fca02414 Mon Sep 17 00:00:00 2001 From: Piotr Mitros Date: Fri, 3 Feb 2012 18:48:44 -0500 Subject: [PATCH 1/4] Calculator changes for Anant --- courseware/capa/calc.py | 40 ++++++++++++++++++++++++++++++++-------- util/views.py | 2 +- 2 files changed, 33 insertions(+), 9 deletions(-) diff --git a/courseware/capa/calc.py b/courseware/capa/calc.py index bd4136d393..41ced03e58 100644 --- a/courseware/capa/calc.py +++ b/courseware/capa/calc.py @@ -1,17 +1,41 @@ +import copy import math import operator +import numpy + 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 +default_functions = {'sin' : numpy.sin, + 'cos' : numpy.cos, + 'tan' : numpy.tan, + 'sqrt': numpy.sqrt, + 'log10':numpy.log10, + 'log2':numpy.log2, + 'ln': numpy.log, + 'arccos':numpy.arccos, + 'arcsin':numpy.arcsin, + 'arctan':numpy.arctan, + 'abs':numpy.abs + } +default_variables = {'j':numpy.complex(0,1), + 'e':numpy.complex(numpy.e) + } + def evaluator(variables, functions, string): ''' Evaluate an expression. Variables are passed as a dictionary from string to value. Unary functions are passed as a dictionary from string to function ''' + all_variables = copy.copy(default_variables) + all_variables.update(variables) + all_functions = copy.copy(default_functions) + all_functions.update(functions) + if string.strip() == "": return float('nan') ops = { "^" : operator.pow, @@ -38,7 +62,7 @@ def evaluator(variables, functions, string): 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 = [e for e in x if type(e) in [float, numpy.float64, numpy.complex]] # Ignore ^ x.reverse() x=reduce(lambda a,b:b**a, x) return x @@ -68,7 +92,7 @@ def evaluator(variables, functions, string): prod=op(prod, e) return prod def func_parse_action(x): - return [functions[x[0]](x[1])] + return [all_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,".-+*/()^") @@ -94,14 +118,14 @@ def evaluator(variables, functions, string): # 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)) + if len(all_variables)>0: + varnames = sreduce(lambda x,y:x|y, map(lambda x: CaselessLiteral(x), all_variables.keys())) + varnames.setParseAction(lambda x:map(lambda y:all_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())) + if len(all_functions)>0: + funcnames = sreduce(lambda x,y:x|y, map(lambda x: CaselessLiteral(x), all_functions.keys())) function = funcnames+lpar.suppress()+expr+rpar.suppress() function.setParseAction(func_parse_action) else: @@ -119,7 +143,7 @@ def evaluator(variables, functions, string): if __name__=='__main__': variables={'R1':2.0, 'R3':4.0} - functions={'sin':math.sin, 'cos':math.cos} + functions={'sin':numpy.sin, 'cos':numpy.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") diff --git a/util/views.py b/util/views.py index 5f0400d143..83d525708a 100644 --- a/util/views.py +++ b/util/views.py @@ -25,7 +25,7 @@ def calculate(request): 'equation':equation} track.views.server_track(request, 'error:calc', event, page='calc') return HttpResponse(json.dumps({'result':'Invalid syntax'})) - return HttpResponse(json.dumps({'result':result})) + return HttpResponse(json.dumps({'result':str(result)})) def send_feedback(request): ''' Feeback mechanism in footer of every page. ''' From 6332fd78dd7fb9e00dd459fcf96ef127b33cf5e1 Mon Sep 17 00:00:00 2001 From: Piotr Mitros Date: Fri, 3 Feb 2012 21:23:05 -0500 Subject: [PATCH 2/4] Fixed CSRF issue most likely introduced by move from Django templates --- courseware/views.py | 1 + django_future/__init__.py | 0 django_future/csrf.py | 81 +++++++++++++++++++++++++++++++++++++++ student/views.py | 9 ++++- 4 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 django_future/__init__.py create mode 100644 django_future/csrf.py diff --git a/courseware/views.py b/courseware/views.py index 8a40e5e9be..62ba58bae2 100644 --- a/courseware/views.py +++ b/courseware/views.py @@ -13,6 +13,7 @@ from django.http import HttpResponse, Http404 from django.shortcuts import redirect from django.template import Context, loader from mitxmako.shortcuts import render_to_response, render_to_string +#from django.views.decorators.csrf import ensure_csrf_cookie from django.db import connection from lxml import etree diff --git a/django_future/__init__.py b/django_future/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/django_future/csrf.py b/django_future/csrf.py new file mode 100644 index 0000000000..ba5c3a7791 --- /dev/null +++ b/django_future/csrf.py @@ -0,0 +1,81 @@ +# Taken from Django 1.4 + +import warnings + +from django.middleware.csrf import CsrfViewMiddleware, get_token +from django.utils.decorators import decorator_from_middleware, available_attrs +from functools import wraps + +csrf_protect = decorator_from_middleware(CsrfViewMiddleware) +csrf_protect.__name__ = "csrf_protect" +csrf_protect.__doc__ = """ +This decorator adds CSRF protection in exactly the same way as +CsrfViewMiddleware, but it can be used on a per view basis. Using both, or +using the decorator multiple times, is harmless and efficient. +""" + + +class _EnsureCsrfToken(CsrfViewMiddleware): + # We need this to behave just like the CsrfViewMiddleware, but not reject + # requests. + def _reject(self, request, reason): + return None + + +requires_csrf_token = decorator_from_middleware(_EnsureCsrfToken) +requires_csrf_token.__name__ = 'requires_csrf_token' +requires_csrf_token.__doc__ = """ +Use this decorator on views that need a correct csrf_token available to +RequestContext, but without the CSRF protection that csrf_protect +enforces. +""" + + +class _EnsureCsrfCookie(CsrfViewMiddleware): + def _reject(self, request, reason): + return None + + def process_view(self, request, callback, callback_args, callback_kwargs): + retval = super(_EnsureCsrfCookie, self).process_view(request, callback, callback_args, callback_kwargs) + # Forces process_response to send the cookie + get_token(request) + return retval + + +ensure_csrf_cookie = decorator_from_middleware(_EnsureCsrfCookie) +ensure_csrf_cookie.__name__ = 'ensure_csrf_cookie' +ensure_csrf_cookie.__doc__ = """ +Use this decorator to ensure that a view sets a CSRF cookie, whether or not it +uses the csrf_token template tag, or the CsrfViewMiddleware is used. +""" + + +def csrf_response_exempt(view_func): + """ + Modifies a view function so that its response is exempt + from the post-processing of the CSRF middleware. + """ + warnings.warn("csrf_response_exempt is deprecated. It no longer performs a " + "function, and calls to it can be removed.", + PendingDeprecationWarning) + return view_func + +def csrf_view_exempt(view_func): + """ + Marks a view function as being exempt from CSRF view protection. + """ + warnings.warn("csrf_view_exempt is deprecated. Use csrf_exempt instead.", + PendingDeprecationWarning) + return csrf_exempt(view_func) + +def csrf_exempt(view_func): + """ + Marks a view function as being exempt from the CSRF view protection. + """ + # We could just do view_func.csrf_exempt = True, but decorators + # are nicer if they don't have side-effects, so we return a new + # function. + def wrapped_view(*args, **kwargs): + return view_func(*args, **kwargs) + wrapped_view.csrf_exempt = True + return wraps(view_func, assigned=available_attrs(view_func))(wrapped_view) diff --git a/student/views.py b/student/views.py index 0ccf51dbb5..b00986ad15 100644 --- a/student/views.py +++ b/student/views.py @@ -15,6 +15,7 @@ from django.shortcuts import redirect from mitxmako.shortcuts import render_to_response, render_to_string from models import Registration, UserProfile +from django_future.csrf import ensure_csrf_cookie log = logging.getLogger("mitx.user") @@ -24,7 +25,7 @@ def csrf_token(context): return '' return u'
' % (csrf_token) - +@ensure_csrf_cookie def index(request): if settings.COURSEWARE_ENABLED and request.user.is_authenticated(): return redirect('/courseware') @@ -44,6 +45,7 @@ def index(request): # 'csrf': csrf_token }) # Need different levels of logging +@ensure_csrf_cookie def login_user(request, error=""): if 'email' not in request.POST or 'password' not in request.POST: return render_to_response('login.html', {'error':error.replace('+',' ')}) @@ -83,11 +85,13 @@ def login_user(request, error=""): return HttpResponse(json.dumps({'success':False, 'error': 'Account not active. Check your e-mail.'})) +@ensure_csrf_cookie def logout_user(request): logout(request) # print len(connection.queries), connection.queries return redirect('/') +@ensure_csrf_cookie def change_setting(request): if not request.user.is_authenticated(): return redirect('/') @@ -104,6 +108,7 @@ def change_setting(request): 'language':up.language, 'location':up.location,})) +@ensure_csrf_cookie def create_account(request, post_override=None): js={'success':False} @@ -221,6 +226,7 @@ def create_random_account(create_account_function): if settings.GENERATE_RANDOM_USER_CREDENTIALS: create_account = create_random_account(create_account) +@ensure_csrf_cookie def activate_account(request, key): r=Registration.objects.filter(activation_key=key) if len(r)==1: @@ -232,6 +238,7 @@ def activate_account(request, key): return render_to_response("activation_invalid.html",{'csrf':csrf(request)['csrf_token']}) return HttpResponse("Unknown error. Please e-mail us to let us know how it happened.") +@ensure_csrf_cookie def password_reset(request): ''' Attempts to send a password reset e-mail. ''' if request.method != "POST": From a9f94da8ea606663b3ae4889e1f3c07a240ce697 Mon Sep 17 00:00:00 2001 From: Piotr Mitros Date: Fri, 3 Feb 2012 22:52:31 -0500 Subject: [PATCH 3/4] Error in conversion from !=None to simpler format --- courseware/module_render.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/courseware/module_render.py b/courseware/module_render.py index 93becb27fa..3c863684be 100644 --- a/courseware/module_render.py +++ b/courseware/module_render.py @@ -90,7 +90,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 not instance.get_score(): + if instance.get_score(): s.grade=instance.get_score()['score'] s.save() # Return whatever the module wanted to return to the client/caller From f894353f4e0d2521ab7cae09903aef6ec23db66f Mon Sep 17 00:00:00 2001 From: Piotr Mitros Date: Fri, 3 Feb 2012 23:01:26 -0500 Subject: [PATCH 4/4] No cache of courseware. This caused lots of issues --- courseware/views.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/courseware/views.py b/courseware/views.py index 62ba58bae2..4975736914 100644 --- a/courseware/views.py +++ b/courseware/views.py @@ -15,6 +15,7 @@ from django.template import Context, loader from mitxmako.shortcuts import render_to_response, render_to_string #from django.views.decorators.csrf import ensure_csrf_cookie from django.db import connection +from django.views.decorators.cache import cache_control from lxml import etree @@ -32,6 +33,7 @@ etree.set_default_parser(etree.XMLParser(dtd_validation=False, load_dtd=False, template_imports={'urllib':urllib} +@cache_control(no_cache=True, no_store=True, must_revalidate=True) 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 .''' @@ -208,6 +210,7 @@ def render_accordion(request,course,chapter,section): return {'init_js':render_to_string('accordion_init.js',context), 'content':render_to_string('accordion.html',context)} +@cache_control(no_cache=True, no_store=True, must_revalidate=True) def index(request, course="6.002 Spring 2012", chapter="Using the System", section="Hints"): ''' Displays courseware accordion, and any associated content. '''