Monday, December 14, 2020

BeDjango: Top 6 Django Decorators

What is a Decorator?

A decorator is the name of one of the most popular design patterns used nowadays, many times we use it without knowing that we are using a design pattern. And what's so special about this pattern? As we can read at  Python Wiki using It is a way of apparently modifying an object's behavior, by enclosing it inside a decorating object with a similar interface. You can get more information about Pattern Design here.

Why I should use decorators in my web application?

Decorators dynamically alter the functionality of a function, method or class without having to make subclasses or change the source code of the decorated class. Thanks to this our code will be more cleaner, more readable, maintainable (Which is no small thing), and reduce the boilerplate code allowing us to add functionality to multiple classes using a single method.
A good example of the importance and easy of use of these decorators can be seen in the decorator @login_required that provides django, and that you probably used if you have some experience with our favorite framework. It’s just a piece of code where we check if the user is not authenticated the user is redirected to the login url.

The way that the decorators as used is the following:

from django.contrib.auth.decorators import login_required


@login_required
def my_view(request)
    …

Each time that a user try to access to my_view, the code inside login_required will be ejecuted.

Some of our favorite decorators

In this section we will show you some of the decorators that we think are most useful or that we have ever used with positive results, keep in mind that many of these can be customized to suit your needs. For this post we will use the original decorators with their font.

Group Required

Sometimes we need to protect some views, to allow a certain group of users to access it. Instead of checking within it if the user belongs to that group/s, we can use the following decorator

from django.contrib.auth.decorators import user_passes_test


def group_required(*group_names):
   """Requires user membership in at least one of the groups passed in."""

   def in_groups(u):
       if u.is_authenticated():
           if bool(u.groups.filter(name__in=group_names)) | u.is_superuser:
               return True
       return False
   return user_passes_test(in_groups)


# The way to use this decorator is:
@group_required(‘admins’, ‘seller’)
def my_view(request, pk)
    ...

You can get more information about it here

Anonymous required

This decorator is based on the decorator login_required of Django, but looks for the opposite case, that the user is anonymous, otherwise the user is redirected to the website defined in our settings.py and can be useful when we want to protect logged user views, such as the login or registration view

def anonymous_required(function=None, redirect_url=None):

   if not redirect_url:
       redirect_url = settings.LOGIN_REDIRECT_URL

   actual_decorator = user_passes_test(
       lambda u: u.is_anonymous(),
       login_url=redirect_url
   )

   if function:
       return actual_decorator(function)
   return actual_decorator


# The way to use this decorator is:
@anonymous_required
def my_view(request, pk)
    ...

You can get more information about it here

Superuser required

This is the same case as when we want to allow certain groups access to a view, but in this case only super users can visit it.

from django.core.exceptions import PermissionDenied


def superuser_only(function):
  """Limit view to superusers only."""

   def _inner(request, *args, **kwargs):
       if not request.user.is_superuser:
           raise PermissionDenied           
       return function(request, *args, **kwargs)
   return _inner


# The way to use this decorator is:
@superuser_only
def my_view(request):
    ...

You can get more information about it here

Ajax required

This decorator check if the request is an AJAX request, useful decorator when we are working with Javascript frameworks as jQuery and a good way to try to secure our application

from django.http import HttpResponseBadRequest


def ajax_required(f):
   """
   AJAX request required decorator
   use it in your views:

   @ajax_required
   def my_view(request):
       ....

   """   

   def wrap(request, *args, **kwargs):
       if not request.is_ajax():
           return HttpResponseBadRequest()
       return f(request, *args, **kwargs)

   wrap.__doc__=f.__doc__
   wrap.__name__=f.__name__
   return wrap


# The way to use this decorator is:
@ajax_required
def my_view(request):
    ...

You can get more information about it here

Time it

This decorator is very helpful if you need to improve the response time of one of then our views or if you just want to know how long it takes to run.

def timeit(method):

   def timed(*args, **kw):
       ts = time.time()
       result = method(*args, **kw)
       te = time.time()
       print('%r (%r, %r) %2.2f sec' % (method.__name__, args, kw, te - ts))
       return result

   return timed


# The way to use this decorator is:
@timeit
def my_view(request):
    ...

You can get more information about it here

Custom Functionality

The next decorator is just an example about how you can check some permissions or some checks in a easy way and 100% customizable.

Imagine you have a blog, shop, forum.... Where users need to have a number of points in order to write a review, it would be a good way to avoid SPAM for example. We'll create a decorator to check that the user is logged in and has more than 10 points, so you could write a review, otherwise we'll raise a Forbidden

from django.http import HttpResponseForbidden


logger = logging.getLogger(__name__)


def user_can_write_a_review(func):
   """View decorator that checks a user is allowed to write a review, in negative case the decorator return Forbidden"""

   @functools.wraps(func)
   def wrapper(request, *args, **kwargs):
       if request.user.is_authenticated() and request.user.points < 10:
           logger.warning('The {} user has tried to write a review, but does not have enough points to do so'.format( request.user.pk))
           return HttpResponseForbidden()

       return func(request, *args, **kwargs)

   return wrapper

You can get more information about it asking myself at piglesias@emergya.com or via twitter @pypiglesias.  I really hope you found the post interesting or at least curious. From BeDjango we would like to encourage you to share your decorators, ideas or questions as well as interesting topics for future posts



from Planet Python
via read more

No comments:

Post a Comment

TestDriven.io: Working with Static and Media Files in Django

This article looks at how to work with static and media files in a Django project, locally and in production. from Planet Python via read...