Customizing @view_function

Since the @view_function decorator must be placed on all view functions in your system, it’s a great place to do pre-endpoint logic. The decorator was intentionally programmed as a class-based decorator so you can extend it.

Django provide several ways to insert logic into the request process, so be sure to consider which is the cleanest approach for your situation: the approach here, middleware, signals, or another method.

Using Keyword Arguments

Although we normally specify @view_function without any arguments, it can take an arbitrary number of keyword arguments. The following are some examples:

# the normal decorator
@view_function

# ensure the user has a role of "mentor"
@view_function(role='mentors')

# require authenticated access, set response type to text/html
@view_function(auth_required=True, mimetype='text/html')

Through a simple extension, you can access the parameters above and do custom logic–just before process_request is called.

Example: Authenticated Endpoints

Suppose your site requires authentication on nearly every endpoint in the system. Normally, you’d add Django’s @login_required decorator to endpoints, like this:

from django.contrib.auth.decorators import login_required
from django_mako_plus import view_function

@login_required
@view_function
def process_request(request):
    ...

Rather than hope every endpoint gets marked with the decorator, let’s modify DMP’s view function decorator to require access by default. Create the following in a file called lib/router.py:

from django.conf import settings
from django.http import HttpResponseRedirect
from django_mako_plus import view_function
import inspect


class web_endpoint(view_function):
    '''Marks a view function in the system (with auth required by default)'''
    def __init__(self, decorated_func, auth_required=True, *args, **kwargs):
        self.auth_required = auth_required
        super().__init__(decorated_func, *args, **kwargs)

    def __call__(self, request, *args, **kwargs):
        # ensure authenticated
        if self.auth_required and request.user.is_anonymous:
            return HttpResponseRedirect(settings.LOGIN_URL)

        # allow the call to continue
        return super().__call__(request, *args, **kwargs)

Then, use this decorator instead of the normal view function decorator. In fact, do a global search and replace of @view_function, and replace it with @secure_function.

from lib.router import secure_function

@secure_function
def process_request(request):
    ...

When DMP calls your view functions, it now runs lib.router.secure_function.__call__. Our function redirects if the current user isn’t authenticated yet. If the user is authenticated, we call the super’s __call__ method, which runs the view function. Just like that, every endpoint in the system is protected by default. Yeah, you know you rock!

For endpoints that need to allow anonymous access, the auth_required parameter signals that anyone can access the endpoint:

from lib.router import secure_function

@secure_function(auth_required=False)
def process_request(request):
    # even balrogs allowed to pass!