Stay DRY in Django With Decorators

Backend website development will often require a developer to repeat themselves for routine tasks such as per-request authentication. Most of the time these repetitive blocks of code can be encapsulated within a function. Doing so ensures that your code complies with the DRY principle and is easier to maintain.

One of my favorite tools for encapsulating functionality is the Python decorator. I use them in bastions, where applicable, and they’ve helped with maintainability as the application has grown.

Take this view for example:

def index(request):
  if not request.session.get("authenticated"):
    return HttpResponseForbidden()
  # ...

As you can see, we’re performing some form of session authentication to ensure that our user is logged in.

This works great for our single view, but what happens when we expand our application adding more views that require the user to be authenticated? We end up with something like this:

def index(request):
  if not request.session.get("authenticated"):
    return HttpResponseForbidden()

  # ...

def account(request):
  if not request.session.get("authenticated"):
    return HttpResponseForbidden()

  # top secret account section ...

Note: please don’t use the snippets above as an example of proper, secure authentication as it’s only meant to be an example of repetitive code

Taking this approach, our code starts to become repetitive quickly. Not to mention, modifying our authentication process would require multiple changes throughout our codebase. This is never good. In fact, it’s bad.

Luckily each Python 3 installation comes with a plethora of personal assistants that are great at very specific tasks. In this instance, our go-to helper is the decorator.

Let’s take a look at the above examples refactored to use a decorator for authentication:

def authenticate(func):
  def wrapper(*args, **kwargs):
    request = args[0]

    if not request.session.get("authenticated"):
      return HttpResponseForbidden()

    return func(*args, **kwargs)

  return wrapper

@authenticate
def index(request):
  # ...

@authenticate
def account(request):
  # top secret account section ...

Just like that, our code is much cleaner and easier to maintain. If we ever need to make a change in our authentication process, we modify the code within our decorator.

Lastly, Django comes with some of its own decorators offering common functionality out-of-the-box. Check there next time you find yourself repeating blocks of code within your views.

Update 10:45 AM 5/5/2018

This post unfortunately omits one of Python 3’s best decorator features, as pointed out by /u/boxidea on Reddit:

This should also include a mention of functools.wraps

https://docs.python.org/3.6/library/functools.html#functools.wraps

Keeping the docstring and name can be important, especially if you are using tools that automatically generate documentation.

I couldn’t have explained it better myself. Definitely check out functools.wraps the next time you’re needing the help of a decorator. Thanks /u/boxidea!