django-maintenancemode icon indicating copy to clipboard operation
django-maintenancemode copied to clipboard

Better customization

Open aqeelat opened this issue 3 years ago • 0 comments

Hi @bashu

I'm thinking about using a settings object that reads from a dict in django settings. Something like https://github.com/encode/django-rest-framework/blob/master/rest_framework/settings.py

What do you think?

This is because we could try and provide more configuration details such as:

  1. return json instead of html, with custom message
  2. custom methods for bypassing the maintenance mode
  3. turning on/off the methods used in processing the request

This is our current setup:

import re
from abc import abstractmethod
from typing import Type

import django
from django.conf import settings, urls
from django.core.exceptions import MiddlewareNotUsed
from django.http import HttpRequest, JsonResponse
from django.urls import get_resolver, resolvers
from django.utils.deprecation import MiddlewareMixin

from feature_flags import FlagKeys, get_variation


# Borrowed heavily from https://github.com/shanx/django-maintenancemode
# TODO: push the changes here to the repo as contributions.

IGNORE_URLS = tuple(re.compile(u) for u in settings.MAINTENANCE_IGNORE_URLS)


class BaseExclusionHandler:
    def __init__(self, request: HttpRequest):
        self.request = request

    @abstractmethod
    def should_bypass(self) -> bool:
        raise NotImplementedError


class LDExclusionHandler(BaseExclusionHandler):
    def should_bypass(self) -> bool:
        return get_variation(
            FlagKeys.should_bypass_maintenance_mode,
            user_obj=self.request.user,
            default_behavior=False,
            extra_attrs=dict(path=self.request.path_info),
        )


def temporary_unavailable(request):  # noqa
    return HttpResponseTemporaryUnavailable(
        dict(
            message="We will be back soon.",  # TODO: load message from FormSettings
        )
    )


# todo: move to package settings
response_class = JsonResponse  # could also be HttpResponse
exclusion_handler: Type[BaseExclusionHandler] = LDExclusionHandler  # todo: make this a list of handlers
return_view = temporary_unavailable

urls.handler503 = return_view
urls.__all__.append("handler503")


class HttpResponseTemporaryUnavailable(response_class):
    status_code = 503


class MaintenanceModeMiddleware(MiddlewareMixin):
    def __init__(self, get_response):
        if not settings.MAINTENANCE_MODE:
            raise MiddlewareNotUsed()

        super().__init__(get_response=get_response)

    @staticmethod
    def process_request(request: HttpRequest):
        # Allow access if middleware is not activated

        print(request.path_info)
        if exclusion_handler(request).should_bypass():
            return None

        # Check if a path is explicitly excluded from maintenance mode
        for url in IGNORE_URLS:
            if url.match(request.path_info):
                return None

        if django.__version__ < "3.2":
            # Checks if DJANGO version is less than 3.2.0 for breaking change
            resolver = get_resolver()

            callback, param_dict = resolver.resolve_error_handler("503")

            return callback(request, **param_dict)

        else:
            # Default behaviour for django 3.2 and higher
            resolver = resolvers.get_resolver(None)
            resolve = resolver.resolve_error_handler
            callback = resolve("503")

            return callback(request)

aqeelat avatar Sep 23 '22 11:09 aqeelat