Skip to content

Commit c1a538f

Browse files
committed
Merge branch 'main' into davidfischer/switch-to-tailwind
2 parents 8427ca1 + 8423453 commit c1a538f

File tree

26 files changed

+414
-55
lines changed

26 files changed

+414
-55
lines changed

.env/local.sample

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,35 @@
11
# This is a sample of environment variables which are used only to run Docker locally.
22
# These are never used in production.
33

4+
# Django
5+
# ------------------------------------------------------------------------------
6+
# Run Django in production mode (DEBUG=False)
7+
DJANGO_SETTINGS_MODULE=config.settings.prod
8+
49
# Use a strong secret in production
510
SECRET_KEY="this-is-a-bad-secret"
611

7-
# In production, we use postgres but for testing a deployment, using SQLite is fine
8-
DATABASE_URL="sqlite:///db.sqlite3"
12+
13+
# PostgreSQL
14+
# ------------------------------------------------------------------------------
15+
# This must match .env/postgres
16+
DATABASE_URL=pgsql://localuser:localpass@postgres:5432/sandiegopython
17+
18+
19+
# Redis
20+
# ------------------------------------------------------------------------------
21+
REDIS_URL=redis://redis:6379/0
22+
23+
24+
# S3/R2 Media Storage
25+
# ------------------------------------------------------------------------------
26+
# If not empty, S3/R2 will be used for media storage
27+
AWS_S3_ACCESS_KEY_ID=
28+
AWS_S3_SECRET_ACCESS_KEY=
29+
AWS_STORAGE_BUCKET_NAME=
30+
# If using a custom domain for media storage, set the MEDIA_URL
31+
# and AWS_S3_CUSTOM_DOMAIN
32+
AWS_S3_CUSTOM_DOMAIN=
33+
MEDIA_URL=/media/
34+
# The endpoint URL is necessary for Cloudflare R2
35+
AWS_S3_ENDPOINT_URL=

.env/postgres

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# PostgreSQL
2+
# ------------------------------------------------------------------------------
3+
POSTGRES_HOST=postgres
4+
POSTGRES_PORT=5432
5+
POSTGRES_DB=sandiegopython
6+
POSTGRES_USER=localuser
7+
POSTGRES_PASSWORD=localpass

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ dmypy.json
131131
############################################
132132
/node_modules/
133133
/staticfiles/
134-
/pythonsd/media/
134+
/media/
135135
/pythonsd/static/css/
136136
/GIT_COMMIT
137137
/BUILD_DATE

Dockerfile

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,25 +20,27 @@ RUN apt-get install -y --no-install-recommends \
2020
make \
2121
build-essential \
2222
g++ \
23+
postgresql-client \
2324
git
2425

2526
RUN mkdir -p /code
2627

2728
WORKDIR /code
2829

29-
COPY . /code/
30+
# Requirements are installed here to ensure they will be cached.
31+
# https://docs.docker.com/build/cache/#use-the-dedicated-run-cache
32+
COPY ./requirements /requirements
33+
RUN pip install --upgrade pip
34+
RUN --mount=type=cache,target=/root/.cache/pip pip install -r /requirements/deployment.txt
35+
RUN --mount=type=cache,target=/root/.cache/pip pip install -r /requirements/local.txt
3036

31-
# Cache dependencies when building which should result in faster docker builds
32-
RUN --mount=type=cache,target=/root/.cache/pip set -ex && \
33-
pip install --upgrade --no-cache-dir pip && \
34-
pip install -r /code/requirements.txt && \
35-
pip install -r /code/requirements/local.txt
37+
COPY . /code/
3638

3739
# Build JS/static assets
38-
RUN npm install
40+
RUN --mount=type=cache,target=/root/.npm npm install
3941
RUN npm run build
4042

41-
RUN python manage.py collectstatic --noinput
43+
RUN python manage.py collectstatic --noinput --clear
4244

4345
# Run the container unprivileged
4446
RUN addgroup www && useradd -g www www
@@ -52,4 +54,4 @@ RUN date -u +'%Y-%m-%dT%H:%M:%SZ' > BUILD_DATE
5254

5355
EXPOSE 8000
5456

55-
CMD ["gunicorn", "--timeout", "15", "--bind", ":8000", "--workers", "2", "--max-requests", "10000", "--max-requests-jitter", "100", "config.wsgi"]
57+
CMD ["gunicorn", "--timeout", "15", "--bind", ":8000", "--workers", "2", "--max-requests", "10000", "--max-requests-jitter", "100", "--log-file", "-", "--access-logfile", "-", "config.wsgi"]

Makefile

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
1-
.PHONY: help test clean deploy
1+
.PHONY: help test clean dockerbuild dockerserve dockershell deploy
2+
3+
4+
DOCKER_CONFIG=compose.yaml
25

36

47
help:
58
@echo "Please use \`make <target>' where <target> is one of"
69
@echo " test Run the full test suite"
710
@echo " clean Delete assets processed by webpack"
11+
@echo " dockerbuild Build the Docker compose dev environment"
12+
@echo " dockerserve Run the Docker containers for the site"
13+
@echo " (starts a webserver on http://localhost:8000)"
14+
@echo " dockershell Connect to a bash shell on the Django Docker container"
815
@echo " deploy Deploy the app to fly.io"
916

1017

@@ -14,6 +21,21 @@ test:
1421
clean:
1522
rm -rf assets/dist/*
1623

24+
# Build the local multi-container application
25+
# This command can take a while the first time
26+
dockerbuild:
27+
docker compose -f $(DOCKER_CONFIG) build
28+
29+
# You should run "dockerbuild" at least once before running this
30+
# It isn't a dependency because running "dockerbuild" can take some time
31+
dockerserve:
32+
docker compose -f $(DOCKER_CONFIG) up
33+
34+
# Use this command to inspect the container, run management commands,
35+
# or run anything else on the Django container
36+
dockershell:
37+
docker compose -f $(DOCKER_CONFIG) run --rm django /bin/bash
38+
1739
# Build and deploy the production container
1840
deploy:
1941
flyctl deploy

README.md

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,19 @@ you can build the container and run it locally:
4242
cp .env/local.sample .env/local
4343

4444
# Build the docker image for sandiegopython.org
45-
docker buildx build -t sandiegopython.org .
46-
47-
# Start a development server on http://localhost:8000
48-
docker run --env-file=".env/local" --publish=8000:8000 sandiegopython.org
49-
50-
# You can start a shell to the container with the following:
51-
docker run --env-file=".env/local" -it sandiegopython.org /bin/bash
45+
# Use Docker compose to have Redis and PostgreSQL just like in production
46+
# Note: Docker is used in production but Docker compose is just for development
47+
make dockerbuild
48+
49+
# Start a development web server on http://localhost:8000
50+
# Use ctrl+C to stop
51+
make dockerserve
52+
53+
# While the server is running,
54+
# you can start a bash shell to the container with the following:
55+
# Once you have a bash shell, you can run migrations,
56+
# manually connect to the local Postgres database or anything else
57+
make dockershell
5258
```
5359

5460

compose.yaml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Docker Compose Local Development Setup
2+
#
3+
# This starts a local multi-container development environment
4+
# with Postgres, Redis, and Django.
5+
# The configuration comes from .env/local and .env/postgres
6+
#
7+
# To run:
8+
# $ make dockerbuild
9+
# $ make dockerserve
10+
11+
volumes:
12+
local_postgres_data: {}
13+
14+
services:
15+
django:
16+
build:
17+
context: .
18+
dockerfile: ./Dockerfile
19+
image: sandiegopython_local_django
20+
depends_on:
21+
- postgres
22+
env_file:
23+
- ./.env/local
24+
- ./.env/postgres
25+
ports:
26+
- "${SANDIEGOPYTHON_DJANGO_PORT:-8000}:8000"
27+
# Allow us to run `docker attach` and get
28+
# control on STDIN and be able to debug our code with interactive pdb
29+
stdin_open: true
30+
tty: true
31+
32+
postgres:
33+
image: postgres:15.2
34+
volumes:
35+
- local_postgres_data:/var/lib/postgresql/data
36+
env_file:
37+
- ./.env/postgres
38+
39+
redis:
40+
image: redis:5.0

config/settings/base.py

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
Django settings for pythonsd project.
33
44
For more information on this file, see
5-
https://docs.djangoproject.com/en/3.2/topics/settings/
5+
https://docs.djangoproject.com/en/4.2/topics/settings/
66
77
For the full list of settings and their values, see
8-
https://docs.djangoproject.com/en/3.2/ref/settings/
8+
https://docs.djangoproject.com/en/4.2/ref/settings/
99
"""
1010

1111
import json
@@ -20,7 +20,7 @@
2020

2121

2222
# Quick-start development settings - unsuitable for production
23-
# https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/
23+
# https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
2424

2525
# SECURITY WARNING: keep the secret key used in production secret!
2626
# SECURITY WARNING: don't run with debug turned on in production!
@@ -82,47 +82,52 @@
8282

8383

8484
# Database
85-
# https://docs.djangoproject.com/en/3.2/ref/settings/#databases
85+
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases
8686
# --------------------------------------------------------------------------
8787
DATABASES = {"default": dj_database_url.config(default="sqlite:///db.sqlite3")}
88+
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
8889

8990

9091
# Internationalization
91-
# https://docs.djangoproject.com/en/3.2/topics/i18n/
92+
# https://docs.djangoproject.com/en/4.2/topics/i18n/
9293
# --------------------------------------------------------------------------
9394
LANGUAGE_CODE = "en-us"
9495

9596
TIME_ZONE = "America/Los_Angeles"
9697

9798
USE_I18N = True
9899

99-
USE_L10N = True
100-
101100
USE_TZ = True
102101

103102

104103
# Static files (CSS, JavaScript, Images)
105-
# https://docs.djangoproject.com/en/3.2/howto/static-files/
104+
# https://docs.djangoproject.com/en/4.2/howto/static-files/
105+
# https://docs.djangoproject.com/en/4.2/ref/settings/#std-setting-STORAGES
106106
# --------------------------------------------------------------------------
107107
STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles")
108108
STATIC_URL = "/static-files/"
109-
110-
# Due to a bug relating to the manifest not being generated before the tests run
111-
# We can't use CompressedManifestStaticFilesStorage (yet)
112-
STATICFILES_STORAGE = "whitenoise.storage.CompressedStaticFilesStorage"
113109
STATICFILES_DIRS = [
114110
os.path.join(BASE_DIR, "assets", "dist"),
115111
os.path.join(BASE_DIR, "pythonsd", "static"),
116112
# Only available after running `npm install`
117113
os.path.join(BASE_DIR, "node_modules/htmx.org/dist"),
118114
]
119115

120-
MEDIA_URL = "/media/"
116+
STORAGES = {
117+
"default": {
118+
"BACKEND": "django.core.files.storage.FileSystemStorage",
119+
},
120+
"staticfiles": {
121+
"BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage",
122+
},
123+
}
124+
125+
MEDIA_URL = os.environ.get("MEDIA_URL", default="/media/")
121126
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
122127

123128

124129
# Email
125-
# https://docs.djangoproject.com/en/3.2/topics/email/
130+
# https://docs.djangoproject.com/en/4.2/topics/email/
126131
# --------------------------------------------------------------------------
127132
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
128133
DEFAULT_FROM_EMAIL = "[email protected]"
@@ -133,8 +138,8 @@
133138
# A sample logging configuration. The only tangible logging
134139
# performed by this configuration is to send an email to
135140
# the site admins on every HTTP 500 error when DEBUG=False.
136-
# http://docs.djangoproject.com/en/3.2/topics/logging
137-
# https://docs.djangoproject.com/en/3.2/ref/settings/#logging
141+
# http://docs.djangoproject.com/en/4.2/topics/logging
142+
# https://docs.djangoproject.com/en/4.2/ref/settings/#logging
138143
# --------------------------------------------------------------------------
139144
LOGGING = {
140145
"version": 1,

config/settings/prod.py

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from .base import * # noqa
1313

1414

15-
# See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/
15+
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
1616

1717
DEBUG = False
1818
SECRET_KEY = os.environ["SECRET_KEY"]
@@ -34,16 +34,32 @@
3434
ADMIN_URL = os.environ.get("ADMIN_URL", "admin")
3535

3636

37+
# Django-storages
38+
# https://django-storages.readthedocs.io
39+
# --------------------------------------------------------------------------
40+
# Optionally store media files in S3/R2/etc.
41+
AWS_S3_ACCESS_KEY_ID = os.environ.get("AWS_S3_ACCESS_KEY_ID")
42+
AWS_S3_SECRET_ACCESS_KEY = os.environ.get("AWS_S3_SECRET_ACCESS_KEY")
43+
AWS_STORAGE_BUCKET_NAME = os.environ.get("AWS_STORAGE_BUCKET_NAME")
44+
# When using media storage with a custom domain
45+
# set this and set MEDIA_URL
46+
AWS_S3_CUSTOM_DOMAIN = os.environ.get("AWS_S3_CUSTOM_DOMAIN")
47+
# The endpoint URL is necessary for Cloudflare R2
48+
AWS_S3_ENDPOINT_URL = os.environ.get("AWS_S3_ENDPOINT_URL", default=None)
49+
if AWS_S3_ACCESS_KEY_ID and AWS_S3_SECRET_ACCESS_KEY and AWS_STORAGE_BUCKET_NAME:
50+
STORAGES["default"]["BACKEND"] = "storages.backends.s3.S3Storage"
51+
52+
3753
# Database
38-
# https://docs.djangoproject.com/en/3.2/ref/settings/#databases
54+
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases
3955
# --------------------------------------------------------------------------
4056
DATABASES = {"default": dj_database_url.config()}
4157
DATABASES["default"]["ATOMIC_REQUESTS"] = True
4258
DATABASES["default"]["CONN_MAX_AGE"] = 600
4359

4460

4561
# Caching
46-
# https://docs.djangoproject.com/en/3.2/ref/settings/#caches
62+
# https://docs.djangoproject.com/en/4.2/ref/settings/#caches
4763
# http://niwinz.github.io/django-redis/
4864
# --------------------------------------------------------------------------
4965
if "REDIS_URL" in os.environ:
@@ -60,7 +76,7 @@
6076

6177

6278
# Security
63-
# https://docs.djangoproject.com/en/3.2/topics/security/
79+
# https://docs.djangoproject.com/en/4.2/topics/security/
6480
# --------------------------------------------------------------------------
6581
if "SECURE_SSL_HOST" in os.environ:
6682
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
@@ -83,14 +99,14 @@
8399

84100

85101
# Sessions
86-
# https://docs.djangoproject.com/en/3.2/topics/http/sessions/
102+
# https://docs.djangoproject.com/en/4.2/topics/http/sessions/
87103
# Don't put sessions in the database
88104

89105
SESSION_ENGINE = "django.contrib.sessions.backends.signed_cookies"
90106

91107

92108
# Email
93-
# https://docs.djangoproject.com/en/3.2/topics/email/
109+
# https://docs.djangoproject.com/en/4.2/topics/email/
94110
# https://anymail.readthedocs.io/en/stable/
95111
# --------------------------------------------------------------------------
96112
if "SENDGRID_API_KEY" in os.environ:
@@ -100,7 +116,7 @@
100116

101117

102118
# Logging
103-
# http://docs.djangoproject.com/en/3.2/topics/logging
119+
# http://docs.djangoproject.com/en/4.2/topics/logging
104120
# --------------------------------------------------------------------------
105121
LOGGING["loggers"][""]["level"] = "INFO"
106122
LOGGING["loggers"]["pythonsd"]["level"] = "INFO"

0 commit comments

Comments
 (0)