简体   繁体   English

将媒体文件和静态文件从 Amazon S3 提供给 Django/Heroku

[英]Serving media files and static files from Amazon S3 to Django/Heroku

I've got a question about serving media files and static files from an S3 bucket.我有一个关于从 S3 存储桶提供媒体文件和静态文件的问题。 I'd be extremely grateful if anyone could give some pointers about how to solve the problem.如果有人能就如何解决问题提出一些建议,我将不胜感激。

Background背景

  • I've completed a number of tutorials to get a basic idea for how Django works.我已经完成了许多教程来了解 Django 的工作原理。 To cement what I've learnt, I've decided to repurpose a lot of the concepts into a simple web app and deploy it online.为了巩固我所学的知识,我决定将许多概念重新用于一个简单的 Web 应用程序并在线部署。

https://tbdcl-allconf-production.herokuapp.com/ https://tbdcl-allconf-production.herokuapp.com/

https://www.allconf.io/ https://www.allconf.io/

  • The site shows a list of conferences , which have a one-to-many relationship with a conference series (eg DjangoCon parent with DjangoCon 2019 and DjangoCon 2020 as children ).该站点显示了会议列表,这些会议会议系列(例如DjangoCon父级DjangoCon 2019DjangoCon 2020作为子级)具有一对多关系。
  • The conference series has a logo image as part of its model.会议系列有一个徽标图像作为其模型的一部分。 The image is displayed as part of a card in a list of all conferences:该图像显示为所有会议列表中卡片的一部分:

https://tbdcl-allconf-production.herokuapp.com/conferences/ https://tbdcl-allconf-production.herokuapp.com/conferences/

https://www.allconf.io/conferences/ https://www.allconf.io/conferences/

  • These are all added through the admin, as it's not intended for users to be able to update the conference list.这些都是通过管理员添加的,因为它不适合用户更新会议列表。

Problem问题

  • Everything worked fine when working locally, BUT when I deployed to Heroku the logos would not display (even though the files were visible in the admin view in the deployed app).在本地工作时一切正常,但是当我部署到 Heroku 时,徽标不会显示(即使文件在部署的应用程序的管理视图中可见)。
  • A quick search revealed that Heroku cannot serve media files, so I chose the (seemingly) most common approach – adding boto3 and django-storages to my project, and pointing it at an Amazon S3 bucket.快速搜索发现 Heroku 无法提供媒体文件,因此我选择了(看似)最常见的方法——将boto3django- storages添加到我的项目中,并将其指向 Amazon S3 存储桶。
  • I followed a number of popular tutorials to set this up.我遵循了一些流行的教程来设置它。
  • However it looks like the app is still looking at "itself" for the media and static files, not the S3 bucket.但是,看起来该应用程序仍在查看媒体和静态文件的“自身”,而不是 S3 存储桶。 I can tell this because:我可以这么说是因为:

  • Simply, the media files and static files are not in the S3 bucket, even though I've followed the tutorials and added the AWS credentials to Django.简单地说,媒体文件和静态文件不在 S3 存储桶中,即使我已经按照教程将 AWS 凭证添加到 Django。

  • When I open the developer view in Chrome, the source for the images + CSS files looks like the local path (there's no reference to "aws" or "s3" in each file's path).当我在 Chrome 中打开开发人员视图时,图像 + CSS 文件的源看起来像本地路径(每个文件的路径中没有对“aws”或“s3”的引用)。

Possible solutions可能的解决方案

  • After doing some more digging + basic troubleshooting, I think it could be a couple of things:在进行了更多挖掘 + 基本故障排除之后,我认为可能有以下几点:
    1. Whitenoise is still set up in my project (from when I was working locally with only static files). Whitenoise 仍然在我的项目中设置(从我在本地只使用静态文件开始)。 Should this be removed?这个应该去掉吗? Is it conflicting with boto3 and django-storages?它与 boto3 和 django-storages 冲突吗?
    2. Do I need to update my models in some way (eg with the def __save__ method)?我是否需要以某种方式更新我的模型(例如使用def __save__方法)?
    3. Do I need to migrate the database (or any other database task) to update the URLs for each model's image file?我是否需要迁移数据库(或任何其他数据库任务)来更新每个模型图像文件的 URL?
    4. Is the fact that the app is running in a Docker container causing an issue with communicating "out" to the S3 bucket?应用程序在 Docker 容器中运行的事实是否会导致与 S3 存储桶“输出”通信的问题?

Although to be honest, I'm completely lost!虽然说实话,我完全迷路了! I can't find any resources online that explain this issue and how to resolve it.我在网上找不到任何资源来解释这个问题以及如何解决它。

I'd be extremely grateful for any advice.我将非常感谢您的任何建议。

If you need to see the settings.py and conferences/models.py files, I've pasted them below.如果您需要查看settings.pyconferences/models.py文件,我已将它们粘贴在下面。

Many thanks!非常感谢!

settings.py设置.py

import os

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

ENVIRONMENT = os.environ.get('ENVIRONMENT', default='production')

# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = os.environ.get('SECRET_KEY')

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = int(os.environ.get('DEBUG', default=0))

ALLOWED_HOSTS = ['tbdcl-allconf-production.herokuapp.com', '.allconf.io', 'localhost', '127.0.0.1']


# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'whitenoise.runserver_nostatic',
    'django.contrib.staticfiles',

    # Third party
    'debug_toolbar',
    'storages',

    # Local
    'users.apps.UsersConfig',
    'pages.apps.PagesConfig',
    'conferences.apps.ConferencesConfig',
]

MIDDLEWARE = [
    'django.middleware.cache.UpdateCacheMiddleware',
    'django.middleware.security.SecurityMiddleware',
    'whitenoise.middleware.WhiteNoiseMiddleware',
    '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',
    'debug_toolbar.middleware.DebugToolbarMiddleware',
    'django.middleware.cache.FetchFromCacheMiddleware',
]

CACHE_MIDDLEWARE_ALIAS = 'default'
CACHE_MIDDLEWARE_SECONDS = 600
CACHE_MIDDLEWARE_KEY_PREFIX = ''

ROOT_URLCONF = 'allconf_project.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, '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 = 'allconf_project.wsgi.application'


# Database
# https://docs.djangoproject.com/en/2.2/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'SQL_NAME',
        'USER': 'SQL_USER',
        'PASSWORD': 'SQL_PASSWORD',
        'HOST': 'db',
        'PORT': 5432
    }
}

# Password validation
# https://docs.djangoproject.com/en/2.2/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/2.2/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'America/New_York'

USE_I18N = True

USE_L10N = True

USE_TZ = True


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.2/howto/static-files/

USE_S3 = os.environ.get('USE_S3') == 'TRUE'

if USE_S3:
    # aws settings
    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 = os.environ.get('AWS_STORAGE_BUCKET_NAME')
    AWS_S3_FILE_OVERWRITE = False
    AWS_DEFAULT_ACL = None
    AWS_S3_CUSTOM_DOMAIN = f'{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com'
    AWS_S3_OBJECT_PARAMETERS = {'CacheControl': 'max-age=86400'}
    # s3 static settings
    STATIC_LOCATION = 'static'
    STATIC_URL = f'https://{AWS_S3_CUSTOM_DOMAIN}/{STATIC_LOCATION}/'
    STATICFILES_STORAGE = 'hello_django.storage_backends.StaticStorage'
    # s3 public media settings
    PUBLIC_MEDIA_LOCATION = 'media'
    MEDIA_URL = f'https://{AWS_S3_CUSTOM_DOMAIN}/{PUBLIC_MEDIA_LOCATION}/'
    DEFAULT_FILE_STORAGE = 'hello_django.storage_backends.PublicMediaStorage'
    # s3 private media settings
    PRIVATE_MEDIA_LOCATION = 'private'
    PRIVATE_FILE_STORAGE = 'hello_django.storage_backends.PrivateMediaStorage'
else:
    STATIC_URL = '/staticfiles/'
    STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
    MEDIA_URL = '/mediafiles/'
    MEDIA_ROOT = os.path.join(BASE_DIR, 'mediafiles')

STATICFILES_DIRS = (os.path.join(BASE_DIR, 'static'),)
STATICFILES_FINDERS = [
    "django.contrib.staticfiles.finders.FileSystemFinder",
    "django.contrib.staticfiles.finders.AppDirectoriesFinder",
]


# User authentication

AUTH_USER_MODEL = 'users.CustomUser'


# django-debug-toolbar

import socket
hostname, _, ips = socket.gethostbyname_ex(socket.gethostname())
INTERNAL_IPS = [ip[:-1] + "1" for ip in ips]


# production

if ENVIRONMENT == 'production':
    SECURE_BROWSER_XSS_FILTER = True
    X_FRAME_OPTIONS = 'DENY'
    SECURE_SSL_REDIRECT = True
    SECURE_HSTS_SECONDS = 3600
    SECURE_HSTS_INCLUDE_SUBDOMAINS = True
    SECURE_HSTS_PRELOAD = True
    SECURE_CONTENT_TYPE_NOSNIFF = True
    SESSION_COOKIE_SECURE = True
    CSRF_COOKIE_SECURE = True
    SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')


# heroku

import dj_database_url
db_from_env = dj_database_url.config(conn_max_age=500)
DATABASES['default'].update(db_from_env)

storage_backends.py storage_backends.py

from storages.backends.s3boto3 import S3Boto3Storage
from django.conf import settings


class StaticStorage(S3Boto3Storage):
    location = 'static'
    default_acl = 'public-read'


class PublicMediaStorage(S3Boto3Storage):
    location = 'media'
    default_acl = 'public-read'
    file_overwrite = False


class PrivateMediaStorage(S3Boto3Storage):
    location = 'private'
    default_acl = 'private'
    file_overwrite = False
    custom_domain = False

conferences/models.py会议/模型.py

import uuid
from django.conf import settings
from django.contrib.auth import get_user_model
from django.db import models
from django.urls import reverse


class Series(models.Model):
    slug = models.SlugField(null=False, unique=True)
    title = models.CharField(max_length=200)
    description = models.CharField(max_length=300)
    series_logo = models.ImageField(upload_to='series_logos/', blank=True)

    class Meta: 
        indexes = [
            models.Index(fields=['slug'], name='slug_index'),
        ]

    def __str__(self):
        return self.title

    def get_absolute_url(self):
        return reverse('series_detail', kwargs={'slug': self.slug})


class Conference(models.Model):
    id = models.UUIDField(
        primary_key=True,
        default=uuid.uuid4,
        editable=False,
    )
    title = models.CharField(max_length=255)
    start_date = models.DateField()
    end_date = models.DateField()
    location = models.CharField(max_length=255)
    conf_url = models.CharField(max_length=255)
    conf_series = models.ForeignKey(
        Series,
        null=True,
        on_delete=models.CASCADE,
        related_name='conferences',
    )

    def __str__(self):
        return self.title

    def get_absolute_url(self):
        return reverse('conference_detail', args=[str(self.id)])

Well it looks like you still have whitenoise in middleware, so try removing that.好吧,看起来中间件中仍然存在白噪声,因此请尝试将其删除。 I would also double AWS_S3_CUSTOM_DOMAIN against what is shown in AWS.我还会针对 AWS 中显示的内容加倍 AWS_S3_CUSTOM_DOMAIN。 I believe the region should be included in there as well.我认为该地区也应包括在内。

For me, I had this issue seemingly because of django-heroku.对我来说,我遇到这个问题似乎是因为 django-heroku。 I had to update my settings.py file with the following:我必须使用以下内容更新我的 settings.py 文件:

django_heroku.settings(locals(), staticfiles=False)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM