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

Docs for multiple buckets with varying properties, Digital Ocean signed URLs with CDN domain #1075

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
65 changes: 65 additions & 0 deletions docs/backends/amazon-S3.rst
Expand Up @@ -374,6 +374,71 @@ So your bucket file can be organized like as below::
| │ ├── app.js
| │ ├── app.css
| │ └── ...

You could also pass different characteristics to different buckets::

from storages.backends.s3boto3 import S3Boto3Storage
from django.utils.deconstruct import deconstructible

@deconstructible
class StaticStorage(S3Boto3Storage):

bucket_name = my-app-public-bucket
location = static

s3_static_storage = StaticStorage()


@deconstructible
class MediaStorage(S3Boto3Storage):

bucket_name = my-app-public-bucket
location = media

s3_media_storage = MediaStorage()


@deconstructible
class PrivStorage(S3Boto3Storage):

custom_domain = None
signature_version = 's3v4'
bucket_name = my-app-private-bucket
location = media

s3_priv_storage = PrivStorage()

The above setup would use two buckets, one that could use an ``AWS_S3_CUSTOM_DOMAIN`` specified in your settings for static files in one folder and media files in another folder of the same bucket, and a separate private bucket that would ignore the custom domain setting and use signed S3 urls for private media files in a separate bucket.

The resulting hierarchy would look like::

| my-app-public-bucket
| ├── media
| │ ├── public_video.mp4
| │ ├── public_file.pdf
| │ └── ...
| ├── static
| │ ├── app.js
| │ ├── app.css
| │ └── ...
| my-app-private-bucket
| ├── media
| │ ├── private_video.mp4
| │ ├── private_file.pdf
| │ └── ...

The function variables in the ``custom_storage.py`` in this above example can be imported and used as storage targets in models. For instance, perhaps you have installed a media library that provides an abstract media model class, and you want the media files you upload to be private, but their thumbnail images to be in your public bucket with your other public images and static files::

from django.db import models
from custom_storages import s3_priv_storage, s3_media_storage
from a_media_addon.models import AbstractMedia

class CustomMedia(AbstractMedia):

file = models.FileField(storage=s3_priv_storage, verbose_name=_('file'))
thumbnail = models.FileField(storage=s3_media_storage, blank=True, verbose_name=_('thumbnail'))

The above model would upload its media ``file`` to the private bucket and the ``thumbnail`` to the public bucket, in both cases using ``media`` as the folder location, as specified in the respective storage classes in ``custom_storages.py``.


Model
Expand Down
56 changes: 56 additions & 0 deletions docs/backends/digital-ocean-spaces.rst
Expand Up @@ -6,3 +6,59 @@ Digital Ocean Spaces implements the S3 protocol. To use it follow the instructio
- Set ``AWS_S3_REGION_NAME`` to your Digital Ocean region (such as ``nyc3`` or ``sfo2``)
- Set ``AWS_S3_ENDPOINT_URL`` to the value of ``https://${AWS_S3_REGION_NAME}.digitaloceanspaces.com``
- Set the values of ``AWS_ACCESS_KEY_ID`` and ``AWS_SECRET_ACCESS_KEY`` to the corresponding values from Digital Ocean

Signed urls with Digital Ocean CDN domains
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

It is possible to use signed urls with a custom domain on Digital Ocean spaces. Per the `Digital Ocean docs for the Spaces API`_::

.. note::

You can use presigned URLs with the Spaces CDN. To do so, configure your SDK or S3 tool to use the non-CDN endpoint, generate a presigned URL for a GetObject request, then modify the hostname in the URL to be the CDN hostname (<space-name>.<region>.cdn.digitaloceanspaces.com, unless the Space uses a custom hostname).

To accomplish this, consider the following settings in your ``settings.py``::

MEDIAFILES_LOCATION = 'media'
AWS_PRIVSTORAGE_BUCKET_NAME = 'my-app-priv-bucket'
AWS_S3_CUSTOM_DOMAIN = 'cdn.mydomain.com'
AWS_S3_REGION_NAME = 'nyc3'
AWS_S3_ENDPOINT_URL = f'https://{AWS_S3_REGION_NAME}.digitaloceanspaces.com'
AWS_S3_SIGNATURE_VERSION = 's3'

Along with the following custom storage class in ``custom_storages.py`` in the root of your Django project::

from django.conf import settings
from storages.backends.s3boto3 import S3Boto3Storage
from django.utils.deconstruct import deconstructible

@deconstructible
class PrivStorage(S3Boto3Storage):

custom_domain = None
bucket_name = settings.AWS_PRIVSTORAGE_BUCKET_NAME
location = settings.MEDIAFILES_LOCATION

s3_priv_storage = PrivStorage()

Digital Ocean can provide signed urls but not via a custom domain, so we have defined a custom domain but told the storage class not to use it, which will cause the url generating method to return a signed link to the object on Digital Ocean's ``digitaloceanspaces.com`` domain. Then, we could use a template tag filter or an additional method on the storage class to return the signed url, with the Digital Ocean domain replaced by the custom domain. The template tag in ``myapp/templatetags/myapp_tags.py`` might look like::

from django.conf import settings

@register.filter
def cdn_url(value):
if settings.AWS_S3_ENDPOINT_URL in value:
cdn_domain = 'https://' + settings.AWS_S3_CUSTOM_DOMAIN
new_url = value.replace(settings.AWS_S3_ENDPOINT_URL, cdn_domain)
return new_url
else:
return value

In your template(s), you could then use the filter to replace the ``digitaloceanspaces.com`` domain with your custom domain after the signed url has been generated. Assuming your page model has a ``media`` instance which represents the stored object, the above filter would be used like so::

{% load myapp_tags %}

{{ self.media.url|cdn_url }}

If your signed url contains the ``nyc3.digitaloceanspaces.com`` endpoint you specified in ``settings.py``, that hostname will be replaced by your ``AWS_S3_CUSTOM_DOMAIN``, else the media object's url value will be returned unmodified.

.. _Digital Ocean docs for the Spaces API: https://docs.digitalocean.com/products/spaces/resources/s3-sdk-examples/#presigned-url