Source code for cardinal_pythonlib.django.middleware

#!/usr/bin/env python
# cardinal_pythonlib/django/middleware.py

"""
===============================================================================

    Original code copyright (C) 2009-2022 Rudolf Cardinal (rudolf@pobox.com).

    This file is part of cardinal_pythonlib.

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

        https://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.

===============================================================================

**Django middleware classes.**

"""

import logging
import os
from re import compile
import sys
from typing import Optional

# noinspection PyUnresolvedReferences
from django.core.exceptions import ImproperlyConfigured

# noinspection PyUnresolvedReferences
from django.conf import settings

# noinspection PyUnresolvedReferences
from django.contrib.auth import REDIRECT_FIELD_NAME

# noinspection PyUnresolvedReferences
from django.http import HttpResponse, HttpRequest

# noinspection PyUnresolvedReferences
from django.views.debug import technical_500_response

# noinspection PyUnresolvedReferences
from django.urls import reverse

# noinspection PyUnresolvedReferences
from django.utils.cache import add_never_cache_headers

# noinspection PyUnresolvedReferences
from django.utils.deprecation import MiddlewareMixin

from cardinal_pythonlib.logs import BraceStyleAdapter

try:
    from django.contrib.auth.views import redirect_to_login
except RuntimeError:
    if not os.environ.get("_SPHINX_AUTODOC_IN_PROGRESS"):
        raise

log = BraceStyleAdapter(logging.getLogger(__name__))


# =============================================================================
# UserBasedExceptionMiddleware
# =============================================================================
# From p436 of Greenfield & Greenfield (2015), "Two Scoops of Django:
# Best Practices for Django 1.8".


[docs]class UserBasedExceptionMiddleware(MiddlewareMixin): """ Django middleware to report debugging information upon an exception, if the user is a superuser. """ # noinspection PyUnusedLocal @staticmethod def process_exception( request: HttpRequest, exception: Exception ) -> HttpResponse: # noinspection PyUnresolvedReferences if request.user.is_superuser: return technical_500_response(request, *sys.exc_info())
# ============================================================================= # LoginRequiredMiddleware # ============================================================================= # ----------------------------------------------------------------------------- # 1. # ----------------------------------------------------------------------------- """ Middleware to require login for all pages. Copyright (C) 2008 Ryan Witt Licensed under the Creative Commons Attribution 3.0 United States License https://onecreativeblog.com/post/59051248/django-login-required-middleware Modified according to: https://djangosnippets.org/snippets/2845/ """ # EXEMPT_URLS = [compile(settings.LOGIN_URL.lstrip('/'))] # if hasattr(settings, 'LOGIN_EXEMPT_URLS'): # EXEMPT_URLS += [compile(expr) for expr in settings.LOGIN_EXEMPT_URLS] # # # class LoginRequiredMiddleware: # """ # Middleware that requires a user to be authenticated to view any page # other than LOGIN_URL. Exemptions to this requirement can optionally be # specified in settings via a list of regular expressions in # LOGIN_EXEMPT_URLS (which you can copy from your urls.py). # # Requires authentication middleware and template context processors to be # loaded. You'll get an error if they aren't. # """ # def process_request(self, request): # assert hasattr(request, 'user'), "The Login Required middleware\ # requires authentication middleware to be installed. Edit your\ # MIDDLEWARE_CLASSES setting to insert\ # 'django.contrib.auth.middleware.AuthenticationMiddleware'. If that doesn't\ # work, ensure your TEMPLATE_CONTEXT_PROCESSORS setting includes\ # 'django.core.context_processors.auth'." # if not request.user.is_authenticated(): # path = request.path_info.lstrip('/') # if not any(m.match(path) for m in EXEMPT_URLS): # # (1) Simple version: # # return HttpResponseRedirect(settings.LOGIN_URL) # # (2) With 'next' parameter: # path = request.get_full_path() # return redirect_to_login(path, settings.LOGIN_URL, # REDIRECT_FIELD_NAME) # ----------------------------------------------------------------------------- # 2. Alternative! # ----------------------------------------------------------------------------- # https://stackoverflow.com/questions/3214589/ # class LoginRequiredMiddleware(object): # """ # For an exempt view: # def someview(request, *args, **kwargs): # # body of view # someview.login_required = False # # class SomeView(View): # login_required = False # # body of view # """ # def process_view(self, request, view_func, view_args, view_kwargs): # if not getattr(view_func, 'login_required', True): # return None # exempt # return login_required(view_func)(request, *view_args, **view_kwargs) # ----------------------------------------------------------------------------- # 3. RNC; composite of those patterns. # ----------------------------------------------------------------------------- EXEMPT_URLS = [compile(settings.LOGIN_URL.lstrip("/"))] if hasattr(settings, "LOGIN_EXEMPT_URLS"): EXEMPT_URLS += [ compile(expr.lstrip("/")) for expr in settings.LOGIN_EXEMPT_URLS ] # noinspection PyClassHasNoInit
[docs]class LoginRequiredMiddleware(MiddlewareMixin): """ Middleware that requires a user to be authenticated to view any page other than LOGIN_URL. Exemptions to this requirement can optionally be specified in settings via a list of regular expressions in LOGIN_EXEMPT_URLS (which you can copy from your urls.py). Requires authentication middleware and template context processors to be loaded. You'll get an error if they aren't. Other way of doing exemptions, for an exempt view: .. code-block:: python def someview(request, *args, **kwargs): # body of view someview.login_required = False class SomeView(View): login_required = False # body of view """ # noinspection PyUnusedLocal @staticmethod def process_view( request: HttpRequest, view_func, view_args, view_kwargs ) -> Optional[HttpResponse]: # log.critical("LoginRequiredMiddleware.process_view") if not hasattr(request, "user"): raise ImproperlyConfigured( "The Login Required middleware requires authentication " "middleware to be installed. Edit your MIDDLEWARE_CLASSES " "setting to insert " "'django.contrib.auth.middleware.AuthenticationMiddleware'. " "If that doesn't work, ensure your " "TEMPLATE_CONTEXT_PROCESSORS setting includes " "'django.core.context_processors.auth'." ) # noinspection PyUnresolvedReferences if request.user.is_authenticated: # log.critical("is_authenticated") return None # OK if not getattr(view_func, "login_required", True): # log.critical("function exempt from login_requred") return None # OK, exempt path = request.path_info.lstrip("/") # Path might look like 'login/' regardless of Django mount point if any(m.match(path) for m in EXEMPT_URLS): # log.critical("URL exempt from login_requred") return None # OK, exempt fullpath = request.get_full_path() return redirect_to_login( fullpath, reverse(settings.LOGIN_VIEW_NAME), REDIRECT_FIELD_NAME )
# ============================================================================= # DisableClientSideCachingMiddleware # ============================================================================= # https://stackoverflow.com/questions/2095520/fighting-client-side-caching-in-django # noqa
[docs]class DisableClientSideCachingMiddleware(MiddlewareMixin): """ Django middleware to ask the client never to cache headers for this page. """ # noinspection PyUnusedLocal @staticmethod def process_response( request: HttpRequest, response: HttpResponse ) -> HttpResponse: add_never_cache_headers(response) return response