Добавление хранилища Amazon S3 в проект Джанго

В этом руководстве вы узнаете, как использовать сервис Amazon S3 для обработки статических ресурсов и загруженных пользователем файлов, то есть медиа-ресурсов.

Зависимости

Вам нужно будет установить две библиотеки Python:

  • boto3
  • django-storages

Библиотека boto3 — это открытый клиент API для доступа к ресурсам Amazon Web Services (AWS), таким как Amazon S3. Это официальный дистрибутив, поддерживаемый Amazon.

django-storages — это библиотека с открытым исходным кодом для управления бэкэндами хранилищ, такими как Dropbox, OneDrive и Amazon S3. Это очень удобно, поскольку в него встроен API-интерфейс хранилища Django. Другими словами, это облегчит вам жизнь, поскольку не изменит кардинально то, как вы взаимодействуете со статическими или медиаресурсами. Нам нужно будет только добавить несколько параметров конфигурации, и библиотека сделает всю тяжелую работу за нас.

Настройка Amazon S3

Прежде чем мы перейдем к Django, давайте настроим S3. Нам нужно будет создать пользователя, который будет иметь доступ к управлению нашими ресурсами S3.

Войдите на веб-страницу AWS, найдите IAM в списке служб, он указан в разделе «Security, Identity & Compliance (Безопасность, идентификация и соответствие)»:

Перейдите на вкладку «Пользователи» и нажмите кнопку «Добавить пользователя»:

Добавьте имя пользователя и выберите опцию программного доступа:

Нажмите Далее, чтобы перейти к разрешениям. На этом этапе нам нужно создать новую группу с правами доступа S3 и добавить в нее нового пользователя. Следуйте указаниям мастера и нажмите кнопку «Создать группу»:

Определите имя для группы и найдите встроенную политику AmazonS3FullAccess:

Нажмите в группе «Создать», чтобы завершить процесс создания группы, на следующем экране, недавно созданная группа будет отображаться выбранной, сохраните ее и, наконец, нажмите кнопку «Далее: обзор»:

Просмотрите информацию, если все правильно, перейдите к созданию нового пользователя. Далее вы должны увидеть эту информацию:

Запишите всю информацию: пользователь, идентификатор ключа доступа и секретный ключ доступа. Сохраните их на потом.

Нажмите на кнопку Закрыть, и давайте продолжим. Теперь пришло время создать нашу первую «корзину».

Корзина (bucket) — это то, что мы называем контейнером хранения в S3. Мы можем работать с несколькими сегментами в рамках одного проекта Django. Но, по большей части, вам понадобится только одна корзина на сайт.

Нажмите в меню «Сервисы» и найдите S3. Он расположен под хранилищем. Если вы видите экран ниже, вы находитесь в правильном месте.

Нажмите на + Создать корзину, чтобы начать поток. Установите DNS-совместимое имя для вашей корзины. Оно будет использоваться для идентификации ваших активов. В моем случае я выбираю sibtc-static. Таким образом, путь к моим ресурсам будет примерно таким: https://sibtc-static.s3.amazonaws.com/static/.

Оставьте оставшиеся настройки без изменений, перейдите к следующим шагам, просто используя значения по умолчанию, и, наконец, нажмите кнопку «Создать корзину». Далее вы должны увидеть экран ниже:

Давайте оставим это так и начнем работать на стороне Джанго.

Установка

Самый простой способ - установить библиотеки с помощью pip:

pip install boto3
pip install django-storages

Теперь добавьте storages к вашему INSTALLED_APPS внутри модуля settings.py:

settings.py

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

    'storages',
]

Работа только со статическими активами

Это простейший вариант использования. Он работает "из коробки" с минимальной конфигурацией. Вся конфигурация ниже идет внутри модуля settings.py:

settings.py

AWS_ACCESS_KEY_ID = 'AKIAIT2Z5TDYPX3ARJBA'
AWS_SECRET_ACCESS_KEY = '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'
AWS_STORAGE_BUCKET_NAME = 'sibtc-static'
AWS_S3_CUSTOM_DOMAIN = '%s.s3.amazonaws.com' % AWS_STORAGE_BUCKET_NAME
AWS_S3_OBJECT_PARAMETERS = {
    'CacheControl': 'max-age=86400',
}
AWS_LOCATION = 'static'

STATICFILES_DIRS = [
    os.path.join(BASE_DIR, 'mysite/static'),
]
STATIC_URL = 'https://%s/%s/' % (AWS_S3_CUSTOM_DOMAIN, AWS_LOCATION)
STATICFILES_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'

Обратите внимание, что у нас есть конфиденциальная информация, такая как AWS_ACCESS_KEY_ID и AWS_SECRET_ACCESS_KEY. Не следует помещать его непосредственно в файл settings.py или фиксировать в общедоступном хранилище. Вместо этого используйте переменные окружения или используйте библиотеку Python Decouple.

Чтобы проиллюстрировать этот пример использования, я создал минимальный проект Django:

mysite/
 |-- mysite/
 |    |-- static/
 |    |    |-- css/
 |    |    |    +-- app.css
 |    |    +-- img/
 |    |         +-- thumbs-up.png
 |    |-- templates/
 |    |    +-- home.html
 |    |-- __init__.py
 |    |-- settings.py
 |    |-- urls.py
 |    +-- wsgi.py
 +-- manage.py

Как видите, обработка статических файлов должна проходить без проблем:

home.html

{% load static %}<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>S3 Example Static Only</title>
  <link rel="stylesheet" type="text/css" href="{% static 'css/app.css' %}">
</head>
<body>
  <header>
    <h1>S3 Example Static Only</h1>
  </header>
  <main>
    <img src="{% static 'img/thumbs-up.png' %}">
    <h2>It's working!</h2>
  </main>
  <footer>
    <a href="https://simpleisbetterthancomplex.com">www.SimpleIsBetterThanComplex.com</a>
  </footer>
</body>
</html>

Даже если мы используем нашу локальную машину, нам нужно будет выполнить команду collectstatic, так как наш код будет ссылаться на удаленное местоположение:

python manage.py collectstatic

Вы заметите, что процесс копирования займет больше времени, чем обычно. Это ожидаемо. Я удалил Django Admin из INSTALLED_APPS, чтобы пример стал чище. Но если вы попробуете это локально, вы увидите множество файлов, скопированных в вашу корзину S3.

Если мы проверим на веб-сайте AWS, мы увидим наши статические активы там:

И наконец, результат:

Как видите, серверная часть хранилища старается перевести тег шаблона {% static 'img / thumbs-up.png'%} в https://sibtc-static.s3.amazonaws.com/static/img/thumbs- up.png и загрузить его из корзины S3.

В следующем примере вы узнаете, как работать со статическими и медиа-активами.

Работа со статическими и медиаресурсами

Для этого примера я создал новую корзину с именем sibtc-assets.

Конфигурация settings.py будет очень похожа. За исключением того, что мы расширим storages.backends.s3boto3.S3Boto3Storage, добавив несколько пользовательских параметров, чтобы иметь возможность хранить загруженные пользователем файлы, то есть медиаресурсы, в другом месте, а также указывать S3 не переопределять файлы с помощью одно и то же имя

Обычно мне нравится создавать файл storage_backends.py в том же каталоге, что и мой settings.py, и вы можете определить новый бэкэнд хранилища следующим образом:

storage_backends.py

from storages.backends.s3boto3 import S3Boto3Storage

class MediaStorage(S3Boto3Storage):
    location = 'media'
    file_overwrite = False

Теперь в файле settings.py нам нужен новый бэкэнд для опции DEFAULT_FILE_STORAGE:

settings.py

STATICFILES_DIRS = [
    os.path.join(BASE_DIR, 'mysite/static'),
]

AWS_ACCESS_KEY_ID = 'AKIAIT2Z5TDYPX3ARJBA'
AWS_SECRET_ACCESS_KEY = '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'
AWS_STORAGE_BUCKET_NAME = 'sibtc-assets'
AWS_S3_CUSTOM_DOMAIN = '%s.s3.amazonaws.com' % AWS_STORAGE_BUCKET_NAME

AWS_S3_OBJECT_PARAMETERS = {
    'CacheControl': 'max-age=86400',
}

AWS_LOCATION = 'static'
STATICFILES_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
STATIC_URL = "https://%s/%s/" % (AWS_S3_CUSTOM_DOMAIN, AWS_LOCATION)

DEFAULT_FILE_STORAGE = 'mysite.storage_backends.MediaStorage'  # <-- here is where we reference it

Для иллюстрации загрузки файла я создал приложение с именем core и определил следующую модель:

models.py

from django.db import models

class Document(models.Model):
    uploaded_at = models.DateTimeField(auto_now_add=True)
    upload = models.FileField()

Тогда вот как выглядит моё представление:

views.py

from django.contrib.auth.decorators import login_required
from django.views.generic.edit import CreateView
from django.urls import reverse_lazy

from .models import Document


class DocumentCreateView(CreateView):
    model = Document
    fields = ['upload', ]
    success_url = reverse_lazy('home')

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        documents = Document.objects.all()
        context['documents'] = documents
        return context

Шаблон document_form.html:

<form method="post" enctype="multipart/form-data">
  {% csrf_token %}
  {{ form.as_p }}
  <button type="submit">Submit</button>
</form>

<table>
  <thead>
    <tr>
      <th>Name</th>
      <th>Uploaded at</th>
      <th>Size</th>
    </tr>
  </thead>
  <tbody>
    {% for document in documents %}
      <tr>
        <td><a href="{{ document.upload.url }}" target="_blank">{{ document.upload.name }}</a></td>
        <td>{{ document.uploaded_at }}</td>
        <td>{{ document.upload.size|filesizeformat }}</td>
      </tr>
    {% empty %}
      <tr>
        <td colspan="3">No data.</td>
      </tr>
    {% endfor %}
  </tbody>
</table>

Как вы можете видеть, я использую только встроенные ресурсы Django в шаблоне. Вот как выглядит этот шаблон:

Я не буду вдаваться в подробности о загрузке файлов, вы можете прочитать подробное руководство здесь в блоге (см. соответствующие ссылки в конце этой статьи для получения дополнительной информации).

Теперь тестируем загруженные пользователем файлы:

Я создал свой шаблон, чтобы вывести список загруженных файлов, поэтому после того, как пользователь загрузит какое-либо изображение или документ, оно будет отображаться, как на картинке выше.

Затем, если мы нажмем на ссылку, которая является обычной {{ document.upload.url }}, управляемой Django, она отобразит изображение из корзины S3:

Теперь, если мы проверим нашу корзину, то увидим, что есть статический и медиа каталог:

Смешивание публичных активов и частных активов

Используя почти те же понятия, вы определяете некоторые ресурсы, которые будут храниться в хранилище S3 в частном порядке. Смотрите конфигурацию ниже:

storage_backends.py

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

class StaticStorage(S3Boto3Storage):
    location = settings.AWS_STATIC_LOCATION

class PublicMediaStorage(S3Boto3Storage):
    location = settings.AWS_PUBLIC_MEDIA_LOCATION
    file_overwrite = False

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

settings.py

AWS_ACCESS_KEY_ID = 'AKIAIT2Z5TDYPX3ARJBA'
AWS_SECRET_ACCESS_KEY = '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'
AWS_STORAGE_BUCKET_NAME = 'sibtc-assets'
AWS_S3_CUSTOM_DOMAIN = '%s.s3.amazonaws.com' % AWS_STORAGE_BUCKET_NAME

AWS_S3_OBJECT_PARAMETERS = {
    'CacheControl': 'max-age=86400',
}

AWS_STATIC_LOCATION = 'static'
STATICFILES_STORAGE = 'mysite.storage_backends.StaticStorage'
STATIC_URL = "https://%s/%s/" % (AWS_S3_CUSTOM_DOMAIN, AWS_STATIC_LOCATION)

AWS_PUBLIC_MEDIA_LOCATION = 'media/public'
DEFAULT_FILE_STORAGE = 'mysite.storage_backends.PublicMediaStorage'

AWS_PRIVATE_MEDIA_LOCATION = 'media/private'
PRIVATE_FILE_STORAGE = 'mysite.storage_backends.PrivateMediaStorage'

Затем мы можем определить этот новый PrivateMediaStorage непосредственно в модели:

models.py

from django.db import models
from django.conf import settings
from django.contrib.auth.models import User

from mysite.storage_backends import PrivateMediaStorage


class Document(models.Model):
    uploaded_at = models.DateTimeField(auto_now_add=True)
    upload = models.FileField()


class PrivateDocument(models.Model):
    uploaded_at = models.DateTimeField(auto_now_add=True)
    upload = models.FileField(storage=PrivateMediaStorage())
    user = models.ForeignKey(User, related_name='documents')

После загрузки личного файла, если вы попытаетесь получить URL-адрес содержимого, API сгенерирует длинный URL-адрес, срок действия которого истекает через несколько минут.:

Если вы попытаетесь получить к нему доступ напрямую, без параметров, вы получите сообщение об ошибке от AWS:

Выводы

Я надеюсь, что это руководство помогло прояснить некоторые концепции Amazon S3 и помогло вам, по крайней мере, начать работу. Не бойтесь копаться в официальной документации как из boto3, так и из библиотеки django-storages.

Вернуться на верх