A decorator can be used to wrap a simple piece of logic to the decorated function.
It does so by creating a new function, injecting the logic, and calling the original function to retrieve the result.
Example 1
def require_http_methods(request_method_list): """ Decorator to make a view only accept particular request methods. Usage:: @require_http_methods(["GET", "POST"]) def my_view(request): # I can assume now that only GET or POST requests make it this far # ... Note that request methods should be in uppercase. """ def decorator(func): @wraps(func, assigned=available_attrs(func)) def inner(request, *args, **kwargs): if request.method not in request_method_list: logger.warning( 'Method Not Allowed (%s): %s', request.method, request.path, extra={'status_code': 405, 'request': request} ) return HttpResponseNotAllowed(request_method_list) return func(request, *args, **kwargs) return inner return decorator require_GET = require_http_methods(["GET"]) require_GET.__doc__ = "Decorator to require that a view only accepts the GET method." require_POST = require_http_methods(["POST"]) require_POST.__doc__ = "Decorator to require that a view only accepts the POST method."
from django.views.decorators.http import require_GET, require_POST
from django.contrib.auth.decorators import login_required
from django.contrib.admin.views.decorators import staff_member_required
from django.shortcuts import redirect, render
from django.core.urlresolvers import reverse
@require_GET
def index(request):
return HttpResponse("Hello, world!")
@require_GET
@login_required
def dashboard(request):
return render(request, 'dashboard.html')
@require_POST
@staff_member_required
def add(request):
# ...
return redirect(reverse('dashboard'))
For readability, it is often a good practice that this kind of decorator does not change the signature (i.e. parameters and return type) of the decorated function. In the example above each function is a Django view, and after being decorated they are still Django views.
Example 2
(Script available)
import time
import functools
def timed(func):
@functools.wraps(func)
def __new_func(*args, **kwargs):
start = time.time()
try:
return func(*args, **kwargs)
finally:
end = time.time()
print("{} took {} seconds".format(func.__name__, end - start))
return __new_func
@timed
def f(x):
for i in range(10 ** x):
pass
if __name__ == '__main__':
f(5)
f(6)
f(7)
f(8)