Reputation: 65
My Django Application (Largely REST Framework based) is currently producing URLs on the admin page that don't resolve. The expected result is that the Django Admin's login prompt submits the form with a POST to /admin/login
. The resultant URL passed by as the form submission URL by Django is /$default/admin/login
and that returns a 404 with the even more obtuse /$default/$default/admin/login/
.
I'm presuming I have some sort of misconfiguration in either my Django configuration or serverless.yml
.
As per the following serverless.yml
I'm using API Gateway V2, Django through WSGI, and Lambda functions.
service: api
app: api
org: myapp
frameworkVersion: '3'
provider:
name: aws
runtime: python3.8
functions:
serve:
handler: wsgi_handler.handler
timeout: 20
environment:
DB_NAME: ${param:db_name}
DB_PASSWORD: ${param:db_password}
DB_USER: ${param:db_user}
DB_PORT: ${param:db_port}
DB_HOST: ${param:db_host}
events:
- httpApi: "*"
migration:
handler: migrate.handler
timeout: 60
environment:
DB_NAME: ${param:db_name}
DB_PASSWORD: ${param:db_password}
DB_USER: ${param:db_user}
DB_PORT: ${param:db_port}
DB_HOST: ${param:db_host}
custom:
wsgi:
app: myapp.wsgi.application
plugins:
- serverless-python-requirements
- serverless-wsgi
My URLs are pretty standard:
from django.contrib import admin
from django.urls import path, include
from rest_framework.schemas import get_schema_view
schema_view = get_schema_view(
title="MyApp",
description="MyApp Universal API",
version="1.0.0",
)
urlpatterns = [
path("admin/", admin.site.urls),
path("user/", include("myapp.core.urls"), name="user"),
path("openapi", schema_view, name="openapi-schema"),
]
My configuration is even more standard:
import os
from pathlib import Path
from dotenv import load_dotenv
load_dotenv()
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = "not for you :)"
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = ['*']
if 'CODESPACE_NAME' in os.environ:
codespace_name = os.getenv("CODESPACE_NAME")
codespace_domain = os.getenv("GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN")
CSRF_TRUSTED_ORIGINS = [f'https://{codespace_name}-8000.{codespace_domain}']
ROOT_URLCONF = "myapp.urls"
# Application definition
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
'rest_framework',
"myapp.core",
]
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]
X_FRAME_OPTIONS = "ALLOW-FROM preview.app.github.dev"
ROOT_URLCONF = "myapp.urls"
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [BASE_DIR / "myapp" / "templates"],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.debug",
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
],
},
},
]
WSGI_APPLICATION = "myapp.wsgi.application"
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
# Database
# https://docs.djangoproject.com/en/4.1/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': os.environ.get('DB_NAME', 'neondb'),
'USER': os.environ.get('DB_USER', 'postgres'),
'PASSWORD': os.environ.get('DB_PASSWORD'),
'HOST': os.environ.get('DB_HOST', 'localhost'),
'PORT': os.environ.get('DB_PORT', 5432),
}
}
# Password validation
# https://docs.djangoproject.com/en/4.1/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
},
{
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
},
{
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
},
{
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
},
]
# Internationalization
# https://docs.djangoproject.com/en/4.1/topics/i18n/
LANGUAGE_CODE = "en-us"
TIME_ZONE = "UTC"
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.1/howto/static-files/
STATICFILES_DIRS = [
BASE_DIR / "myapp" / "static",
]
STATIC_URL = "static/"
# Default primary key field type
# https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
STATICFILES_STORAGE = 'storages.backends.s3boto3.S3StaticStorage'
AWS_ACCESS_KEY_ID = os.environ.get('AWS_ACCESS_KEY_ID')
AWS_SECRET_ACCESS_KEY = os.environ.get('AWS_SECRET_ACCESS_KEY')
AWS_STORAGE_BUCKET_NAME = "myapp-django-static"
AUTH_USER_MODEL = 'core.User'
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': (
'rest_framework.renderers.JSONRenderer',
),
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticatedOrReadOnly',
)
}
As for the resultant error message:
Any help (or ideas) would be greatly appreciated!
I've tried modifying the path structure of the serverless.yml
and have been trawling through the Django source code for any hints to no avail. Naturally I'd just like Django admin to work. As far as the rest of the app, it works fine as the API itself isn't self referential. Django just isn't returning the correct path.
To put it in brief, Django thinks that all of my routes are prefixed by /$default/
they are not. I'm looking for either a solution to force the path to sent by Django to be /
or a fix for my Serverless configuration to mitigate this issue.
Upvotes: 1
Views: 288
Reputation: 65
I managed to resolve this with a rather niché Django settings option: FORCE_SCRIPT_NAME
While this doesn't explain why it is resolving the path to /$default/
it does mitigate this issue.
If you're using this for a different use case than mine and your path is in a subdirectory (e.g the opposite issue to mine) then you would add your path in FORCE_SCRIPT_NAME
instead.
See the link I've provided for more information.
Add the following to your app's settings.py
, or any file you're using as your DJANGO_SETTINGS_MODULE
.
# Force Django to resolve the URL to the root of the site
# This is required for API Gateway <- WSGI -> Django path resolution to work properly.
FORCE_SCRIPT_NAME = ""
Upvotes: 3