Django 和 AWS S3:静态文件请求错误 URL

Django and AWS S3: Static File Requests to Wrong URL

我正在尝试使用 Python 3.7 应用程序和 AWS Elastic Beanstalk 的 React 16.6 前端为 Django 2.1 提供服务。我使用 Create React App 构建 React 应用程序。

至此我已经成功获取到服务器运行(I serve React's index.html via a TemplateView)并连接到数据库。我现在有点无法将 S3 存储桶连接到我的模板视图。

这是问题所在:

python manage.py collectstatic

工作正常(当我访问默认的 Django 管理页面时,登录的样式正确),但是当我尝试访问我的 React 路由之一时,该页面只是空白。使用 chrome 开发工具,我能够推断出 index.html 已正确加载,但网络请求失败:

GET http://<my-eb-url>.elasticbeanstalk.com/static/js/main.a1cf6ce7.chunk.js net::ERR_ABORTED 404 (Not Found)

很明显,错误是 Django 没有在存储桶中寻找 React,而是在相对文件路径中寻找。我怎样才能改变这个?我的意思是 Python 配置应该不错,因为 collectstatic 有效。

这里是 React 的 index.html:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="shortcut icon" href="/favicon.ico" />
    <meta
      name="viewport"
      content="minimum-scale=1,initial-scale=1,width=device-width,shrink-to-fit=no"
    />
    <meta name="theme-color" content="#000000" />
    <link
      rel="stylesheet"
      href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700|Roboto+Slab:400,700"
    />
    <!-- <link rel="stylesheet" href="/fonts/fonts.css" /> -->
    <link rel="manifest" href="/manifest.json" />
    <title>Pontem</title>
    <link href="/static/css/main.0ebf21be.chunk.css" rel="stylesheet" />
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
    <script>
      !(function(l) {
        function e(e) {
          for (var r, t, n = e[0], o = e[1], u = e[2], f = 0, i = []; f < n.length; f++)
            (t = n[f]), p[t] && i.push(p[t][0]), (p[t] = 0);
          for (r in o) Object.prototype.hasOwnProperty.call(o, r) && (l[r] = o[r]);
          for (s && s(e); i.length; ) i.shift()();
          return c.push.apply(c, u || []), a();
        }
        function a() {
          for (var e, r = 0; r < c.length; r++) {
            for (var t = c[r], n = !0, o = 1; o < t.length; o++) {
              var u = t[o];
              0 !== p[u] && (n = !1);
            }
            n && (c.splice(r--, 1), (e = f((f.s = t[0]))));
          }
          return e;
        }
        var t = {},
          p = { 2: 0 },
          c = [];
        function f(e) {
          if (t[e]) return t[e].exports;
          var r = (t[e] = { i: e, l: !1, exports: {} });
          return l[e].call(r.exports, r, r.exports, f), (r.l = !0), r.exports;
        }
        (f.m = l),
          (f.c = t),
          (f.d = function(e, r, t) {
            f.o(e, r) || Object.defineProperty(e, r, { enumerable: !0, get: t });
          }),
          (f.r = function(e) {
            "undefined" != typeof Symbol &&
              Symbol.toStringTag &&
              Object.defineProperty(e, Symbol.toStringTag, { value: "Module" }),
              Object.defineProperty(e, "__esModule", { value: !0 });
          }),
          (f.t = function(r, e) {
            if ((1 & e && (r = f(r)), 8 & e)) return r;
            if (4 & e && "object" == typeof r && r && r.__esModule) return r;
            var t = Object.create(null);
            if (
              (f.r(t),
              Object.defineProperty(t, "default", { enumerable: !0, value: r }),
              2 & e && "string" != typeof r)
            )
              for (var n in r)
                f.d(
                  t,
                  n,
                  function(e) {
                    return r[e];
                  }.bind(null, n)
                );
            return t;
          }),
          (f.n = function(e) {
            var r =
              e && e.__esModule
                ? function() {
                    return e.default;
                  }
                : function() {
                    return e;
                  };
            return f.d(r, "a", r), r;
          }),
          (f.o = function(e, r) {
            return Object.prototype.hasOwnProperty.call(e, r);
          }),
          (f.p = "/");
        var r = (window.webpackJsonp = window.webpackJsonp || []),
          n = r.push.bind(r);
        (r.push = e), (r = r.slice());
        for (var o = 0; o < r.length; o++) e(r[o]);
        var s = n;
        a();
      })([]);</script
    ><script src="/static/js/1.f9c0bd2f.chunk.js"></script
    ><script src="/static/js/main.a1cf6ce7.chunk.js"></script>
  </body>
</html>

附录 我觉得错误不是由 Python 引起的。但仅仅因为它可能有帮助,这里是我的 python 设置。还有我的 pip 包。

base.py:

"""
Django settings for hrdinner project.
"""

import os
import sys
from pathlib import Path

from django.core.exceptions import ImproperlyConfigured


def get_env_variable(var_name):
    """Get the environment variable or return exception."""
    try:
        return os.environ[var_name]
    except KeyError:

        error_msg = 'Set the {} environment variable'.format(var_name)
        raise ImproperlyConfigured(error_msg)


# Build paths inside the project like this: BASE_DIR / 'media'
BASE_DIR = Path(__file__).resolve().parent.parent

# Tell Django where to look for the apps.
sys.path.append(str(BASE_DIR.parent / 'src'))

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = get_env_variable('DJANGO_SECRET_KEY')

ALLOWED_HOSTS = []

# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'model_utils',
    'authtools',
    'rest_framework',
    'rest_framework.authtoken',

    # my apps ...
]

ROOT_URLCONF = 'config.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR.parent / 'build'],
        '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 = 'config.wsgi.application'

ATOMIC_REQUESTS = True

SESSION_ENGINE = "django.contrib.sessions.backends.cache"
SESSION_CACHE_ALIAS = "default"

# Password validation

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',
    },
]

AUTHENTICATION_BACKENDS = (
    # Needed to login by username in Django admin, regardless of `allauth`
    'django.contrib.auth.backends.ModelBackend',
)

LOGIN_URL = "/"

DEBUG_PROPAGATE_EXCEPTIONS = False

# Internationalization
# https://docs.djangoproject.com/en/1.11/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = True

# Static files (CSS, JavaScript, Images)

STATICFILES_DIRS = [BASE_DIR.parent / 'build' / 'static']

# Security

X_FRAME_OPTIONS = 'DENY'

# django-authtools configuration

AUTH_USER_MODEL = 'accounts.User'

# django-rest-framework configuration

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.TokenAuthentication',
        'rest_framework.authentication.BasicAuthentication',
        'rest_framework.authentication.SessionAuthentication',
    ),
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated',
    )
}

production.py:

from .aws.conf import *
from .base import *

DEBUG = False

INSTALLED_APPS += [
    'storages',
]

ALLOWED_HOSTS += [
    # my urls
]

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': get_env_variable('RDS_DB_NAME'),
        'USER': get_env_variable('RDS_USERNAME'),
        'PASSWORD': get_env_variable('RDS_PASSWORD'),
        'HOST': get_env_variable('RDS_HOSTNAME'),
        'PORT': get_env_variable('RDS_PORT'),
    }
}

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',
]


SESSION_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
CSRF_COOKIE_SECURE = True
CSRF_COOKIE_HTTPONLY = True
SECURE_SSL_HOST = True
# TODO: CHANGE THIS TO A YEAR ONCE YOUR ARE READY! 5 minutes for testing.
SECURE_HSTS_SECONDS = 300
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_CONTENT_TYPE_NOSNIFF = True

conf.py:

import os

from django.core.exceptions import ImproperlyConfigured


def get_env_variable(var_name):
    """Get the environment variable or return exception."""
    try:
        return os.environ[var_name]
    except KeyError:
        error_msg = 'Set the {} environment variable'.format(var_name)
        raise ImproperlyConfigured(error_msg)


AWS_ACCESS_KEY_ID = get_env_variable("ACCESS_KEY_ID")
AWS_SECRET_ACCESS_KEY = get_env_variable("SECRET_ACCESS_KEY")
AWS_S3_SIGNATURE_VERSION = 's3v4'
AWS_S3_OBJECT_PARAMETERS = {
    'CacheControl': 'max-age=86400',
}
AWS_STORAGE_BUCKET_NAME = get_env_variable("AWS_BUCKET_NAME")
AWS_S3_CUSTOM_DOMAIN = '%s.s3.eu-central-1.amazonaws.com' % AWS_STORAGE_BUCKET_NAME
AWS_LOCATION = 'static'

STATIC_URL = 'https://%s/%s/' % (AWS_S3_CUSTOM_DOMAIN, AWS_LOCATION)
STATICFILES_STORAGE = 'config.settings.aws.utils.StaticRootS3BotoStorage'

DEFAULT_FILE_STORAGE = 'config.settings.aws.utils.MediaRootS3BotoStorage'
MEDIA_URL = 'https://%s.s3.amazonaws.com/media/' % AWS_STORAGE_BUCKET_NAME
MEDIA_ROOT = MEDIA_URL

ADMIN_MEDIA_PREFIX = STATIC_URL + 'admin/'

utils.py:

from storages.backends.s3boto3 import S3Boto3Storage


def StaticRootS3BotoStorage(): return S3Boto3Storage(location='static')


def MediaRootS3BotoStorage(): return S3Boto3Storage(location='media')

requirements.txt:

awsebcli==3.14.6
blessed==1.15.0
boto==2.49.0
boto3==1.9.42
botocore==1.12.42
cached-property==1.5.1
cement==2.8.2
certifi==2018.10.15
chardet==3.0.4
colorama==0.3.9
Django==2.1.2
django-annoying==0.10.4
django-authtools==1.6.0
django-model-utils==3.1.2
django-storages==1.7.1
djangorestframework==3.9.0
docker==3.5.1
docker-compose==1.21.2
docker-pycreds==0.3.0
dockerpty==0.4.1
docopt==0.6.2
docutils==0.14
idna==2.6
isort==4.3.4
jmespath==0.9.3
jsonschema==2.6.0
pathspec==0.5.5
psycopg2==2.7.5
python-dateutil==2.7.5
pytz==2018.6
PyYAML==3.13
requests==2.18.4
s3transfer==0.1.13
semantic-version==2.5.0
termcolor==1.1.0
texttable==0.9.1
urllib3==1.22
wcwidth==0.1.7
websocket-client==0.54.0

您的浏览器正在尝试从相对路径获取您的样式表和 javascript 文件,因为您在 index.html:

中指定了一个
<script src="/static/js/1.f9c0bd2f.chunk.js"></script>

由于您使用 Django 来收集静态文件,因此您也可以在模板中使用它的 template tag to reference static files

<script src="{% static 'js/1.f9c0bd2f.chunk.js' %}"></script>