Harry Percival @hjwp www.obeythetestinggoat.com www.pythonanywhere.com
Decorators, don't you love them?
url: /user/<username>/my_stuff
def owner_or_admin(view):
@wraps(view)
def wrapped_view(request, username, *args, **kwargs):
if request.user.username != username and not request.user.is_staff:
return HttpResponseForbidden(render_to_string("403.html", context_instance=RequestContext(request)))
return view(request, get_object_or_404(User, username=username), *args, **kwargs)
return wrapped_view
@owner_or_admin
def my_private_stuff(request, owner):
#bla
But testing:
class MyPrivateStuffViewTest(TestCase):
def test_non_owner_cannot_view(self):
self.client.login(non-user,whatever)
response = self.client.get('/user/realuser/my_stuff/')
self.assertEqual(response.status_code, 403)
def test_non_owner_cannot_view(self):
self.client.login(admin_user,whatever)
self.client.get('/user/realuser/my_stuff/')
self.assertEqual(response.status_code, 200)
For every view that's decorated! No thanks!
How about this?
def owner_or_admin(view):
@wraps(view)
def wrapped_view(request, username, *args, **kwargs):
if request.user.username != username and not request.user.is_staff:
return HttpResponseForbidden(render_to_string("403.html", context_instance=RequestContext(request)))
wrapped_view.decorated_with_owner_or_admin = True
return view(request, get_object_or_404(User, username=username), *args, **kwargs)
def test_view_is_decorated_with_owner_or_admin
from app.views import my_stuff
self.assertTrue(my_stuff.decorated_with_owner_or_admin)
Better!
But what about all my other decorators?
@authorized_user
@create_new_profile_if_necessary
@definitely_not_send_all_info_to_nsa
Can you see where this is going?
PRESENTING....
@baroque, THE DECORATING DECORATOR DECORATOR
class TestBaroque(unittest.TestCase):
def test_decorates_decorated_function_with_name_of_decorators(self):
@baroque
def my_decorator(func):
return func
@my_decorator
def my_function():
pass
self.assertEquals(my_function.decorated_by, set(["my_decorator"]))
def test_works_for_multiple_decorators(self):
@baroque
def my_decorator1(func):
return func
@baroque
def my_decorator2(func):
return func
@my_decorator1
@my_decorator2
def my_function():
pass
self.assertEquals(
my_function.decorated_by,
set(["my_decorator1", "my_decorator2"])
)
def test_class_based_decorators(self):
#...
def test_decorators_that_take_arguments(self):
#... this is hard!
And don't forget this test:
def test_decorated_decorator_still_works(self):
@baroque
def decorator(func):
def inner(*args):
return func(*args) + 2
return inner
@decorator
def foo(x):
return x + 1
self.assertEquals(foo(2), 5)
Implementation is left as an exercise for the reader....