Skip to content

Commit

Permalink
Merge pull request #20 from commonknowledge/fix/async-middleware
Browse files Browse the repository at this point in the history
fix: make middleware async-compatible to fix asgi
  • Loading branch information
joaquimds authored Mar 11, 2024
2 parents af657ad + 5d63a15 commit c6886db
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 61 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.venv
__pycache__
.env
.env.local
.static/**/*
Expand Down
95 changes: 81 additions & 14 deletions hub/middleware.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,99 @@
import logging
from datetime import timedelta
from inspect import isawaitable

from django.http import HttpRequest
from django.utils.decorators import sync_and_async_middleware
from django.utils.timezone import now

from asgiref.sync import iscoroutinefunction, sync_to_async
from gqlauth.core.middlewares import USER_OR_ERROR_KEY, UserOrError
from gqlauth.core.middlewares import django_jwt_middleware as _django_jwt_middleware
from gqlauth.core.types_ import GQLAuthError, GQLAuthErrors
from whitenoise.middleware import WhiteNoiseMiddleware

from hub.models import UserProperties

logger = logging.getLogger(__name__)

class RecordLastSeenMiddleware:
one_day = timedelta(hours=24)

def __init__(self, get_response):
self.get_response = get_response
@sync_and_async_middleware
def record_last_seen_middleware(get_response):
one_day = timedelta(hours=24)

def __call__(self, request):
def process_request(request):
if request.user.is_authenticated:
user = request.user
if not hasattr(user, "userproperties"):
UserProperties.objects.create(user=user)

props = UserProperties.objects.get_or_create(user=user)
last_seen = request.session.get("last_seen", None)

yesterday = now().replace(hour=0, minute=0) - self.one_day

yesterday = now().replace(hour=0, minute=0) - one_day
if last_seen is None or last_seen < yesterday.timestamp():
props = user.userproperties
props.last_seen = now()
request.session["last_seen"] = props.last_seen.timestamp()
props.save()

response = self.get_response(request)
if iscoroutinefunction(get_response):
async def middleware(request: HttpRequest):
await sync_to_async(process_request)(request)
return await get_response(request)

else:
def middleware(request: HttpRequest):
process_request(request)
return get_response(request)

return middleware


@sync_and_async_middleware
def async_whitenoise_middleware(get_response):
def logic(request):
return WhiteNoiseMiddleware(get_response)(request)

if iscoroutinefunction(get_response):
async def middleware(request: HttpRequest):
response = logic(request)
if isawaitable(response):
response = await response
return response

else:
def middleware(request: HttpRequest):
return logic(request)

return middleware


@sync_and_async_middleware
def django_jwt_middleware(get_response):
"""
Wrap the gqlauth jwt middleware in an exception
handler (initially added because if a user is
deleted, the middleware throws an error,
causing a 500 instead of a 403).
"""
gqlauth_middleware = _django_jwt_middleware(get_response)

def exception_handler(error: Exception, request: HttpRequest):
logger.warning(f"Gqlauth middleware error: {error}")
user_or_error = UserOrError()
user_or_error.error = GQLAuthError(code=GQLAuthErrors.UNAUTHENTICATED)
setattr(request, USER_OR_ERROR_KEY, user_or_error)

if iscoroutinefunction(get_response):
async def middleware(request: HttpRequest):
try:
return await gqlauth_middleware(request)
except Exception:
exception_handler(request)
return await get_response(request)

else:
def middleware(request: HttpRequest):
try:
return gqlauth_middleware(request)
except Exception as e:
exception_handler(request)
return get_response(request)

return response
return middleware
33 changes: 3 additions & 30 deletions local_intelligence_hub/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
DEBUG=(bool, False),
ALLOWED_HOSTS=(list, []),
CORS_ALLOWED_ORIGINS=(list, ['http://localhost:3000']),
HIDE_DEBUG_TOOLBAR=(bool, False),
GOOGLE_ANALYTICS=(str, ""),
GOOGLE_SITE_VERIFICATION=(str, ""),
TEST_AIRTABLE_BASE_ID=(str, ""),
Expand All @@ -50,7 +49,6 @@
ALLOWED_HOSTS = env("ALLOWED_HOSTS")
CORS_ALLOWED_ORIGINS = env("CORS_ALLOWED_ORIGINS")
CACHE_FILE = env("CACHE_FILE")
HIDE_DEBUG_TOOLBAR = env("HIDE_DEBUG_TOOLBAR")
MAPIT_URL = env("MAPIT_URL")
MAPIT_API_KEY = env("MAPIT_API_KEY")
GOOGLE_ANALYTICS = env("GOOGLE_ANALYTICS")
Expand Down Expand Up @@ -105,16 +103,16 @@

MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"whitenoise.middleware.WhiteNoiseMiddleware",
"hub.middleware.async_whitenoise_middleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"corsheaders.middleware.CorsMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"gqlauth.core.middlewares.django_jwt_middleware",
"hub.middleware.django_jwt_middleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"hub.middleware.RecordLastSeenMiddleware",
"hub.middleware.record_last_seen_middleware",
]

ROOT_URLCONF = "local_intelligence_hub.urls"
Expand Down Expand Up @@ -235,31 +233,6 @@
EMAIL_PORT = env.str("EMAIL_PORT", 1025)
DEFAULT_FROM_EMAIL = env.str("DEFAULT_FROM_EMAIL", "webmaster@localhost")

if DEBUG and HIDE_DEBUG_TOOLBAR is False: # pragma: no cover
hostname, _, ips = socket.gethostbyname_ex(socket.gethostname())
INTERNAL_IPS = [ip[:-1] + "1" for ip in ips] + ["127.0.0.1", "10.0.2.2"]
CSRF_TRUSTED_ORIGINS = ["https://*.preview.app.github.dev"]

# debug toolbar has to come after django_hosts middleware
MIDDLEWARE.insert(1, "debug_toolbar.middleware.DebugToolbarMiddleware")

INSTALLED_APPS += ("debug_toolbar",)

DEBUG_TOOLBAR_PANELS = [
"debug_toolbar.panels.versions.VersionsPanel",
"debug_toolbar.panels.timer.TimerPanel",
"debug_toolbar.panels.settings.SettingsPanel",
"debug_toolbar.panels.headers.HeadersPanel",
"debug_toolbar.panels.request.RequestPanel",
"debug_toolbar.panels.sql.SQLPanel",
"debug_toolbar.panels.staticfiles.StaticFilesPanel",
"debug_toolbar.panels.templates.TemplatesPanel",
"debug_toolbar.panels.cache.CachePanel",
"debug_toolbar.panels.signals.SignalsPanel",
"debug_toolbar.panels.logging.LoggingPanel",
"debug_toolbar.panels.redirects.RedirectsPanel",
]

POSTCODES_IO_URL = "https://postcodes.commonknowledge.coop"
POSTCODES_IO_BATCH_MAXIMUM = 100

Expand Down
17 changes: 1 addition & 16 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ strawberry-graphql = {extras = ["asgi"], version = "^0.220.0"}
pandas = "^2.2.1"

[tool.poetry.dev-dependencies]
django-debug-toolbar = "^3.7.0"
black = "^22.8.0"
coverage = "^6.5.0"
flake8 = "^5.0.4"
Expand Down

0 comments on commit c6886db

Please sign in to comment.