Skip to content

Commit d87863e

Browse files
authored
Merge pull request #66 from HackSoftware/file-uploads
File uploads - locally and on s3
2 parents 40d1532 + bc83158 commit d87863e

File tree

28 files changed

+705
-46
lines changed

28 files changed

+705
-46
lines changed

.env.example

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,10 @@
11
PYTHONBREAKPOINT=ipdb.set_trace
22
SENTRY_DSN=""
3+
4+
FILE_UPLOAD_STRATEGY="direct" # pass-thru
5+
FILE_UPLOAD_STORAGE="local" # s3
6+
7+
AWS_S3_ACCESS_KEY_ID=""
8+
AWS_S3_SECRET_ACCESS_KEY=""
9+
AWS_STORAGE_BUCKET_NAME="django-styleguide-example"
10+
AWS_S3_REGION_NAME="eu-central-1"

.github/workflows/django.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ jobs:
88
- name: Build docker
99
run: docker-compose build
1010
- name: Type check
11-
run: docker-compose run django mypy styleguide_example/
11+
run: docker-compose run django mypy --config mypy.ini styleguide_example/
1212
- name: Run migrations
1313
run: docker-compose run django python manage.py migrate
1414
- name: Run tests
@@ -38,7 +38,9 @@ jobs:
3838
python -m pip install --upgrade pip
3939
pip install -r requirements/local.txt
4040
- name: Type check
41-
run: mypy styleguide_example/
41+
run: |
42+
mypy --version
43+
mypy --config mypy.ini styleguide_example/
4244
- name: Run migrations
4345
run: python manage.py migrate
4446
- name: Run tests

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,7 @@ dmypy.json
127127

128128
# Pyre type checker
129129
.pyre/
130+
131+
132+
# media files
133+
/media

config/django/base.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,7 @@
1212

1313
import os
1414

15-
from config.env import env, environ
16-
17-
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
18-
BASE_DIR = environ.Path(__file__) - 3
15+
from config.env import env, BASE_DIR
1916

2017
env.read_env(os.path.join(BASE_DIR, ".env"))
2118

@@ -41,6 +38,8 @@
4138
'styleguide_example.users.apps.UsersConfig',
4239
'styleguide_example.errors.apps.ErrorsConfig',
4340
'styleguide_example.testing_examples.apps.TestingExamplesConfig',
41+
'styleguide_example.integrations.apps.IntegrationsConfig',
42+
'styleguide_example.files.apps.FilesConfig',
4443
]
4544

4645
THIRD_PARTY_APPS = [
@@ -171,8 +170,12 @@
171170
'DEFAULT_AUTHENTICATION_CLASSES': []
172171
}
173172

173+
APP_DOMAIN = env("APP_DOMAIN", default="http://localhost:8000")
174+
174175
from config.settings.cors import * # noqa
175176
from config.settings.jwt import * # noqa
176177
from config.settings.sessions import * # noqa
177178
from config.settings.celery import * # noqa
178179
from config.settings.sentry import * # noqa
180+
181+
from config.settings.files_and_storages import * # noqa

config/env.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
1+
from django.core.exceptions import ImproperlyConfigured
2+
13
import environ
24

35
env = environ.Env()
6+
7+
BASE_DIR = environ.Path(__file__) - 2
8+
9+
10+
def env_to_enum(enum_cls, value):
11+
for x in enum_cls:
12+
if x.value == value:
13+
return x
14+
15+
raise ImproperlyConfigured(f"Env value {repr(value)} could not be found in {repr(enum_cls)}")

config/settings/files_and_storages.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import os
2+
3+
from config.env import BASE_DIR, env, env_to_enum
4+
5+
from styleguide_example.files.enums import FileUploadStrategy, FileUploadStorage
6+
7+
8+
FILE_UPLOAD_STRATEGY = env_to_enum(
9+
FileUploadStrategy,
10+
env("FILE_UPLOAD_STRATEGY", default="direct")
11+
)
12+
FILE_UPLOAD_STORAGE = env_to_enum(
13+
FileUploadStorage,
14+
env("FILE_UPLOAD_STORAGE", default="local")
15+
)
16+
17+
if FILE_UPLOAD_STORAGE == FileUploadStorage.LOCAL:
18+
MEDIA_ROOT_NAME = "media"
19+
MEDIA_ROOT = os.path.join(BASE_DIR, MEDIA_ROOT_NAME)
20+
MEDIA_URL = f"/{MEDIA_ROOT_NAME}/"
21+
22+
if FILE_UPLOAD_STORAGE == FileUploadStorage.S3:
23+
# Using django-storages
24+
# https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html
25+
DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
26+
27+
AWS_S3_ACCESS_KEY_ID = env("AWS_S3_ACCESS_KEY_ID")
28+
AWS_S3_SECRET_ACCESS_KEY = env("AWS_S3_SECRET_ACCESS_KEY")
29+
AWS_STORAGE_BUCKET_NAME = env("AWS_STORAGE_BUCKET_NAME")
30+
AWS_S3_REGION_NAME = env("AWS_S3_REGION_NAME")
31+
AWS_S3_SIGNATURE_VERSION = env("AWS_S3_SIGNATURE_VERSION", default="s3v4")
32+
33+
# https://docs.aws.amazon.com/AmazonS3/latest/userguide/acl-overview.html#canned-acl
34+
AWS_DEFAULT_ACL = env("AWS_DEFAULT_ACL", default="private")
35+
36+
AWS_PRESIGNED_EXPIRY = env.int("AWS_PRESIGNED_EXPIRY", default=10) # seconds

config/urls.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@
1414
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
1515
"""
1616
from django.contrib import admin
17+
from django.conf import settings
1718
from django.urls import path, include
19+
from django.conf.urls.static import static
1820

1921
urlpatterns = [
2022
path('admin/', admin.site.urls),
2123
path('api/', include(('styleguide_example.api.urls', 'api'))),
22-
]
24+
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

mypy.ini

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
[mypy]
2+
plugins =
3+
mypy_django_plugin.main,
4+
mypy_drf_plugin.main
5+
6+
[mypy.plugins.django-stubs]
7+
django_settings_module = "config.django.base"
8+
9+
[mypy-config.*]
10+
# Ignore everything related to Django config
11+
ignore_errors = true
12+
13+
[mypy-styleguide_example.*.migrations.*]
14+
# Ignore Django migrations
15+
ignore_errors = true
16+
17+
[mypy-celery.*]
18+
# Remove this when celery stubs are present
19+
ignore_missing_imports = True
20+
21+
[mypy-django_celery_beat.*]
22+
# Remove this when django_celery_beat stubs are present
23+
ignore_missing_imports = True
24+
25+
[mypy-django_filters.*]
26+
# Remove this when django_filters stubs are present
27+
ignore_missing_imports = True
28+
29+
[mypy-factory.*]
30+
# Remove this when factory stubs are present
31+
ignore_missing_imports = True
32+
33+
[mypy-rest_framework_jwt.*]
34+
# Remove this when rest_framework_jwt stubs are present
35+
ignore_missing_imports = True

requirements/base.txt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@ django-celery-beat==2.2.1
1010
whitenoise==6.0.0
1111

1212
django-filter==21.1
13-
django-cors-headers==3.11.0
1413
django-extensions==3.1.5
14+
django-cors-headers==3.10.0
15+
django-storages==1.12.3
1516

1617
drf-jwt==1.19.2
18+
19+
boto3==1.20.20
20+
attrs==21.4.0

requirements/local.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@ ipython==8.2.0
1414
mypy==0.942
1515
django-stubs==1.9.0
1616
djangorestframework-stubs==1.4.0
17+
boto3-stubs==1.21.32

0 commit comments

Comments
 (0)