Skip to content

Commit

Permalink
Add view for togging Django maintenance mode
Browse files Browse the repository at this point in the history
  • Loading branch information
epicserve committed Oct 9, 2023
1 parent 4231999 commit 8ee16b9
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 1 deletion.
35 changes: 35 additions & 0 deletions apps/accounts/middleware.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import base64
import binascii

from django.contrib.auth import authenticate
from django.http import JsonResponse
from django.utils.deprecation import MiddlewareMixin
from django.utils.encoding import smart_str


class BasicAuthenticationMiddleware(MiddlewareMixin):
@staticmethod
def process_request(request):
if "HTTP_AUTHORIZATION" in request.META:
auth_parts = request.META.get("HTTP_AUTHORIZATION", "").split()
auth_error = "Unauthorized"

if not auth_parts or len(auth_parts) != 2 or auth_parts[0].lower() != "basic":
return JsonResponse({"error": "Unauthorized"}, status=401)
else:
username_password = smart_str(auth_parts[1])
try:
username_password = base64.b64decode(username_password).decode("utf8")
username, password = username_password.split(":", 1)
user = authenticate(request, username=username, password=password)
except binascii.Error:
auth_error = (
"Invalid basic header: Username and password must be base64 encoded with a colon delimiter "
"(e.g. 'username:password')."
)
user = None

if user is not None:
request.user = user
else:
return JsonResponse({"error": auth_error}, status=401)
30 changes: 30 additions & 0 deletions apps/base/views.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import json

from django.contrib.auth.mixins import UserPassesTestMixin
from django.http import JsonResponse
from django.shortcuts import render
from django.utils.decorators import method_decorator
from django.views import generic
from django.views.decorators.csrf import csrf_exempt

from maintenance_mode.core import set_maintenance_mode


class IndexView(generic.TemplateView):
Expand All @@ -12,3 +20,25 @@ def http_500(request):

def http_404(request):
return render(request, "404.html")


class ToggleMaintenanceModeView(UserPassesTestMixin, generic.View):
raise_exception = True
# curl -X POST -H "Content-Type: application/json" -d '{"maintenance_mode": "off"}' -u "email:password" http://127.0.0.1:8000/maintenance-mode/

def test_func(self):
return self.request.user.is_superuser

@method_decorator(csrf_exempt)
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

def post(self, request, *args, **kwargs):
data = json.loads(self.request.body.decode("utf-8"))
target_mode = data.get("maintenance_mode", None)
if target_mode is None or target_mode not in ("on", "off"):
return JsonResponse({"error": "Invalid request: maintenance_mode must be 'on' or 'off'"}, status=400)

target_mode = True if target_mode == "on" else False
set_maintenance_mode(target_mode)
return JsonResponse({"maintenance_mode": target_mode})
2 changes: 2 additions & 0 deletions config/settings/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"apps.accounts.middleware.BasicAuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"maintenance_mode.middleware.MaintenanceModeMiddleware",
Expand Down Expand Up @@ -299,3 +300,4 @@
# MAINTENANCE MODE SETTINGS
MAINTENANCE_MODE_STATE_BACKEND = "maintenance_mode.backends.CacheBackend"
MAINTENANCE_MODE_STATE_BACKEND_FALLBACK_VALUE = True
MAINTENANCE_MODE_IGNORE_URLS = ("/maintenance-mode/",)
3 changes: 2 additions & 1 deletion config/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from django.views.generic import TemplateView

from apps.accounts.views import NameChange
from apps.base.views import http_404, http_500
from apps.base.views import ToggleMaintenanceModeView, http_404, http_500

# Includes
urlpatterns: list[Union[URLResolver, URLPattern]] = [path(r"admin/", admin.site.urls)]
Expand All @@ -19,6 +19,7 @@
path("404/", http_404),
path("accounts/name/", NameChange.as_view(), name="account_change_name"),
path("accounts/", include("allauth.urls")),
path("maintenance-mode/", ToggleMaintenanceModeView.as_view(), name="maintenance_mode"),
]

# Debug/Development URLs
Expand Down

0 comments on commit 8ee16b9

Please sign in to comment.