Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: make middleware async-compatible to fix asgi #20

Merged
merged 1 commit into from
Mar 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading