Skip to content

Commit

Permalink
added django-storages to serve static and media via bucketeer (s3)
Browse files Browse the repository at this point in the history
  • Loading branch information
Dan Starner committed Jan 13, 2022
1 parent 299bbe2 commit 265becc
Show file tree
Hide file tree
Showing 7 changed files with 260 additions and 24 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Django specific
collected-static
collected-media
.vscode

# Byte-compiled / optimized / DLL files
__pycache__/
Expand Down
59 changes: 36 additions & 23 deletions app.json
Original file line number Diff line number Diff line change
@@ -1,25 +1,38 @@
{
"name": "Proper Django Static File Serving",
"description": "Using bucketeer with private/public media and static files in Django",
"repository": "https://github.com/dstarner/django-heroku-static-file-example",
"logo": "https://i0.wp.com/copyassignment.com/wp-content/uploads/2021/08/Django-logo.jpg?fit=474%2C474&ssl=1",
"keywords": ["python", "django", "static"],
"addons": ["heroku-postgresql:hobby-dev"],
"buildpacks": [
{"url": "https://github.com/moneymeets/python-poetry-buildpack"},
{"url": "heroku/python"}
],
"env": {
"SECRET_KEY": {
"description": "A secret key value required by Django",
"generator": "secret"
},
"DEBUG": {
"value": "False"
},
"DJANGO_SUPERUSER_PASSWORD": {
"description": "Used to generate the superuser of username 'test' (test@example.com)",
"value": "hunter2"
}
}
"name": "Proper Django Static File Serving",
"description": "Using bucketeer with private/public media and static files in Django",
"repository": "https://github.com/dstarner/django-heroku-static-file-example",
"logo": "https://i0.wp.com/copyassignment.com/wp-content/uploads/2021/08/Django-logo.jpg?fit=474%2C474&ssl=1",
"keywords": ["python", "django", "static"],
"addons": [
"heroku-postgresql:hobby-dev",
{
"plan": "bucketeer:hobbyist",
"as": "BUCKETEER"
}
],
"buildpacks": [{
"url": "https://github.com/moneymeets/python-poetry-buildpack"
},
{
"url": "heroku/python"
}
],
"env": {
"SECRET_KEY": {
"description": "A secret key value required by Django",
"generator": "secret"
},
"DEBUG": {
"value": "False"
},
"DJANGO_SUPERUSER_PASSWORD": {
"description": "Used to generate the superuser of username 'test' (test@example.com)",
"value": "hunter2"
},
"S3_ENABLED": {
"description": "Enable to upload & serve static and media files from S3",
"value": "True"
}
}
}
36 changes: 36 additions & 0 deletions example/config/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import os
from pathlib import Path

from decouple import config
import django_heroku

# Build paths inside the project like this: BASE_DIR / 'subdir'.
Expand Down Expand Up @@ -121,6 +122,41 @@

STATIC_ROOT = BASE_DIR / 'collected-static'

S3_ENABLED = config('S3_ENABLED', cast=bool, default=False)
LOCAL_SERVE_MEDIA_FILES = config('LOCAL_SERVE_MEDIA_FILES', cast=bool, default=not S3_ENABLED)
LOCAL_SERVE_STATIC_FILES = config('LOCAL_SERVE_STATIC_FILES', cast=bool, default=not S3_ENABLED)

if (not LOCAL_SERVE_MEDIA_FILES or not LOCAL_SERVE_STATIC_FILES) and not S3_ENABLED:
raise ValueError('S3_ENABLED must be true if either media or static files are not served locally')

if S3_ENABLED:
AWS_ACCESS_KEY_ID = config('BUCKETEER_AWS_ACCESS_KEY_ID')
AWS_SECRET_ACCESS_KEY = config('BUCKETEER_AWS_SECRET_ACCESS_KEY')
AWS_STORAGE_BUCKET_NAME = config('BUCKETEER_BUCKET_NAME')
AWS_S3_REGION_NAME = config('BUCKETEER_AWS_REGION')
AWS_DEFAULT_ACL = None
AWS_S3_SIGNATURE_VERSION = config('S3_SIGNATURE_VERSION', default='s3v4')
AWS_S3_ENDPOINT_URL = f'https://{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com'
AWS_S3_OBJECT_PARAMETERS = {'CacheControl': 'max-age=86400'}

if not LOCAL_SERVE_STATIC_FILES:
STATIC_DEFAULT_ACL = 'public-read'
STATIC_LOCATION = 'static'
STATIC_URL = f'{AWS_S3_ENDPOINT_URL}/{STATIC_LOCATION}/'
STATICFILES_STORAGE = 'example.utils.storage_backends.StaticStorage'

if not LOCAL_SERVE_MEDIA_FILES:
PUBLIC_MEDIA_DEFAULT_ACL = 'public-read'
PUBLIC_MEDIA_LOCATION = 'media/public'

MEDIA_URL = f'{AWS_S3_ENDPOINT_URL}/{PUBLIC_MEDIA_LOCATION}/'
DEFAULT_FILE_STORAGE = 'rn_api.utils.storage_backends.PublicMediaStorage'

PRIVATE_MEDIA_DEFAULT_ACL = 'private'
PRIVATE_MEDIA_LOCATION = 'media/private'
PRIVATE_FILE_STORAGE = 'rn_api.utils.storage_backends.PrivateMediaStorage'


# Default primary key field type
# https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field

Expand Down
8 changes: 8 additions & 0 deletions example/config/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,17 @@
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.conf import settings
from django.conf.urls.static import static
from django.contrib import admin
from django.urls import path

urlpatterns = [
path('admin/', admin.site.urls),
]

if settings.LOCAL_SERVE_STATIC_FILES:
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

if settings.LOCAL_SERVE_MEDIA_FILES:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
26 changes: 26 additions & 0 deletions example/utils/storage_backends.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from django.conf import settings
from storages.backends.s3boto3 import S3Boto3Storage


class StaticStorage(S3Boto3Storage):
"""Used to manage static files for the web server"""
location = settings.STATIC_LOCATION
default_acl = settings.STATIC_DEFAULT_ACL


class PublicMediaStorage(S3Boto3Storage):
"""Used to store & serve dynamic media files with no access expiration"""
location = settings.PUBLIC_MEDIA_LOCATION
default_acl = settings.PUBLIC_MEDIA_DEFAULT_ACL
file_overwrite = False


class PrivateMediaStorage(S3Boto3Storage):
"""
Used to store & serve dynamic media files using access keys
and short-lived expirations to ensure more privacy control
"""
location = settings.PRIVATE_MEDIA_LOCATION
default_acl = settings.PRIVATE_MEDIA_DEFAULT_ACL
file_overwrite = False
custom_domain = False
151 changes: 150 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ Django = "^4.0.1"
gunicorn = "^20.1.0"
Pillow = "^9.0.0"
django-heroku = "^0.3.1"
django-storages = "^1.12.3"
boto3 = "^1.20.34"
python-decouple = "^3.5"

[tool.poetry.dev-dependencies]

Expand Down

0 comments on commit 265becc

Please sign in to comment.