How do I apply Django middleware everywhere except for a single path?

Viewed 307

I'm using Python 3.9 with Django 3. I have defined this middleware ...

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'directory.middleware.extend_token_response.ExtendTokenResponse'
]

However, I don't want the middleware to apply to a certain URL. I have hard-coded this in the middleware like so

class ExtendTokenResponse:
    def __init__(self, get_response):
        self.get_response = get_response
        # One-time configuration and initialization.

    def __call__(self, request):
        response = self.get_response(request)
        if request.path != '/' + LOGOUT_PATH:
            # Code to be executed for each request before
            # the view (and later middleware) are called.
            is_expired = True
            try:
                token = request.auth
                print("req path: %s" % request.path)
                is_expired = is_token_expired(token) if token else True
            except Exception as err:
                print(err)
            if not is_expired:

but this seems a little sloppy and I would think the middleware comes with somethign out of the box to configure that this wouldn't need to be applied to my "/logout" path. Is there a more elegant way to configure this?

Edit: In response to Bernhard Vallant's answer, I changed my middleware to the below

def token_response_exempt(view_func):
    # Set an attribute on the function to mark it as exempt
    def wrapped_view(*args, **kwargs):
        return view_func(*args, **kwargs)

    wrapped_view.token_response_exempt = True
    return wraps(view_func)(wrapped_view)

class ExtendTokenResponse: def init(self, get_response): self.get_response = get_response # One-time configuration and initialization.

def process_view(self, request, view_func, view_args, view_kwargs):
    print("in process view method ...\n")
    if getattr(view_func, "token_response_exempt", False):
        print("returning none ...\n")
        return None
    # Code to be executed for each request before
    # the view (and later middleware) are called.
    is_expired = True
    try:
        token = request.auth
        print("req path: %s" % request.path)
        is_expired = is_token_expired(token) if token else True
    except Exception as err:
        print(err)
    if not is_expired:
        token.delete()
        new_token = Token.objects.create(user = token.user)

        # Code to be executed for each request/response after
        # the view is called.
        print("setting new token to %s" % new_token)
        request.token = new_token 

def __call__(self, request):
    response = self.get_response(request)
    print("---- in call method ----\n")
    if getattr(request, "token", None) is not None:
        print("setting refresh token header = %s" % request.token)
        response['Refresh-Token'] = request.token
    return response

but any call to an endpoint, e.g.,

curl --header "Content-type: application/json" --data "$req" --request POST "http://localhost:8000/login"

results in no token being retrieved from the reqeust. "request.auth" generates the error

'WSGIRequest' object has no attribute 'auth'
2 Answers

Django itself doesn't provide a solution for this. Probably hardcoding/defining paths in your settings/middleware is fine as long it is a middleware that primarly exists for one specific project.

However if you want to mark certain views to exclude them from being processed you could use decorators in the same way Django does with the csrf_exempt decorator.

from functools import wraps

def token_response_exempt(view_func):
    # Set an attribute on the function to mark it as exempt
    def wrapped_view(*args, **kwargs):
        return view_func(*args, **kwargs)

    wrapped_view.token_response_exempt = True
    return wraps(view_func)(wrapped_view)

# your middleware
class ExtendTokenResponse:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        response = self.get_response(request)
        if getattr(request, "token", None) is not None:
            response['Refresh-Token'] = request.token
        return response

    def process_view(self, request, view_func, view_args, view_kwargs):
         if getattr(view_func, "token_response_exempt", False):
            return None
         # do your token generation here
         request.token = token

And then you can use decorator like the following:

# urls.py

urlpatterns = [
    path('logout/', token_response_exempt(LogOutView.as_view())),
]

About your case, I have 2 recommendations below:

Method 1: use process_view and define a list func will be excluded with structure "app.module.func" and check to skip in process_view

# In settings.py
EXCLUDE_FROM_MY_MIDDLEWARE =set({'custom_app.views.About'})

# In middlewares.py
class ExtendTokenResponse:
    def __init__(self, get_response):
        self.get_response = get_response
        # One-time configuration and initialization.

    def __call__(self, request):
        # Code to be executed for each request before
        # the view (and later middleware) are called.
        logger.info(f'request hit request {request}')
        response = self.get_response(request)

        # Code to be executed for each request/response after
        # the view is called.

        return response

    def process_view(self, request, view_func, view_args, view_kwargs):

        view_function = '.'.join((view_func.__module__, view_func.__name__))
        exclusion_set=getattr(settings,'EXCLUDE_FROM_MY_MIDDLEWARE',set() )
        if view_function in exclusion_set:
            return None

Method 2: Use decorator_from_middleware and apply middleware to each function needed it.

from django.utils.decorators import decorator_from_middleware
# with function view
@decorator_from_middleware(ExtendTokenResponse)
def view_function(request):
...
#with class view
class SimpleMiddlewareMixin:
    @decorator_from_middleware(ExtendTokenResponse) 
    def dispatch(*args, **kwargs):
        return super().dispatch(*args, **kwargs)

class MyClassBasedView(SimpleMiddlewareMixin, ListView):

Related