codefortify-starter 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. codefortify_starter/__init__.py +7 -0
  2. codefortify_starter/__main__.py +6 -0
  3. codefortify_starter/cli.py +103 -0
  4. codefortify_starter/constants.py +48 -0
  5. codefortify_starter/generator.py +246 -0
  6. codefortify_starter/template_engine.py +63 -0
  7. codefortify_starter/templates/base_project/README.md.jinja +87 -0
  8. codefortify_starter/templates/base_project/apps/__init__.py +1 -0
  9. codefortify_starter/templates/base_project/apps/home/__init__.py +1 -0
  10. codefortify_starter/templates/base_project/apps/home/apps.py.jinja +7 -0
  11. codefortify_starter/templates/base_project/apps/home/templates/home/index.html.jinja +25 -0
  12. codefortify_starter/templates/base_project/apps/home/tests.py.jinja +17 -0
  13. codefortify_starter/templates/base_project/apps/home/urls.py.jinja +14 -0
  14. codefortify_starter/templates/base_project/apps/home/views.py.jinja +13 -0
  15. codefortify_starter/templates/base_project/core/__init__.py.jinja +7 -0
  16. codefortify_starter/templates/base_project/core/asgi.py.jinja +10 -0
  17. codefortify_starter/templates/base_project/core/env.py.jinja +46 -0
  18. codefortify_starter/templates/base_project/core/middleware/__init__.py +1 -0
  19. codefortify_starter/templates/base_project/core/middleware/exceptions.py +26 -0
  20. codefortify_starter/templates/base_project/core/middleware/security.py +12 -0
  21. codefortify_starter/templates/base_project/core/settings/__init__.py +1 -0
  22. codefortify_starter/templates/base_project/core/settings/base.py.jinja +160 -0
  23. codefortify_starter/templates/base_project/core/settings/dev.py.jinja +8 -0
  24. codefortify_starter/templates/base_project/core/settings/production.py.jinja +21 -0
  25. codefortify_starter/templates/base_project/core/templates/errors/400.html +8 -0
  26. codefortify_starter/templates/base_project/core/templates/errors/403.html +8 -0
  27. codefortify_starter/templates/base_project/core/templates/errors/404.html +8 -0
  28. codefortify_starter/templates/base_project/core/templates/errors/405.html +8 -0
  29. codefortify_starter/templates/base_project/core/templates/errors/500.html +8 -0
  30. codefortify_starter/templates/base_project/core/urls.py.jinja +23 -0
  31. codefortify_starter/templates/base_project/core/views.py.jinja +56 -0
  32. codefortify_starter/templates/base_project/core/wsgi.py.jinja +10 -0
  33. codefortify_starter/templates/base_project/dot-env.example.jinja +66 -0
  34. codefortify_starter/templates/base_project/dot-gitignore +34 -0
  35. codefortify_starter/templates/base_project/manage.py.jinja +16 -0
  36. codefortify_starter/templates/base_project/static/css/main.css +19 -0
  37. codefortify_starter/templates/base_project/templates/base.html.jinja +19 -0
  38. codefortify_starter/templates/features/celery/apps/home/tasks.py.jinja +7 -0
  39. codefortify_starter/templates/features/celery/core/celery.py.jinja +16 -0
  40. codefortify_starter/templates/features/docker/DOCKER_README.md.jinja +67 -0
  41. codefortify_starter/templates/features/docker/Dockerfile.jinja +20 -0
  42. codefortify_starter/templates/features/docker/docker-compose.prod.yml.jinja +158 -0
  43. codefortify_starter/templates/features/docker/docker-compose.yml.jinja +208 -0
  44. codefortify_starter/templates/features/docker/docker_deploy.sh.jinja +132 -0
  45. codefortify_starter/templates/features/docker/dot-dockerignore +12 -0
  46. codefortify_starter/templates/features/docker/entrypoint.sh.jinja +95 -0
  47. codefortify_starter/templates/features/drf/apps/api/__init__.py +1 -0
  48. codefortify_starter/templates/features/drf/apps/api/apps.py.jinja +7 -0
  49. codefortify_starter/templates/features/drf/apps/api/serializers.py +7 -0
  50. codefortify_starter/templates/features/drf/apps/api/tests.py +11 -0
  51. codefortify_starter/templates/features/drf/apps/api/urls.py +11 -0
  52. codefortify_starter/templates/features/drf/apps/api/views.py +16 -0
  53. codefortify_starter/templates/features/htmx/templates/partials/example.html.jinja +5 -0
  54. codefortify_starter/validators.py +70 -0
  55. codefortify_starter-1.0.0.dist-info/METADATA +276 -0
  56. codefortify_starter-1.0.0.dist-info/RECORD +60 -0
  57. codefortify_starter-1.0.0.dist-info/WHEEL +5 -0
  58. codefortify_starter-1.0.0.dist-info/entry_points.txt +2 -0
  59. codefortify_starter-1.0.0.dist-info/licenses/LICENSE +22 -0
  60. codefortify_starter-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,20 @@
1
+ FROM python:3.12-slim
2
+
3
+ ENV PYTHONDONTWRITEBYTECODE=1 \
4
+ PYTHONUNBUFFERED=1 \
5
+ PIP_NO_CACHE_DIR=1
6
+
7
+ WORKDIR /app
8
+
9
+ COPY requirements.txt /app/requirements.txt
10
+ RUN pip install --upgrade pip && pip install -r /app/requirements.txt
11
+
12
+ COPY . /app
13
+
14
+ RUN chmod +x /app/entrypoint.sh /app/docker_deploy.sh
15
+
16
+ EXPOSE 8000
17
+
18
+ ENTRYPOINT ["/app/entrypoint.sh"]
19
+ CMD ["gunicorn", "core.wsgi:application", "--bind", "0.0.0.0:8000", "--workers", "3", "--timeout", "120"]
20
+
@@ -0,0 +1,158 @@
1
+ name: [[ project_slug ]]
2
+
3
+ services:
4
+ web:
5
+ build:
6
+ context: .
7
+ dockerfile: Dockerfile
8
+ command: gunicorn core.wsgi:application --bind 0.0.0.0:8000 --workers ${GUNICORN_WORKERS:-3} --timeout ${GUNICORN_TIMEOUT:-120}
9
+ env_file:
10
+ - .env
11
+ environment:
12
+ CODEFORTIFY_ENVIRONMENT: production
13
+ DEBUG: "False"
14
+ ALLOWED_HOSTS: ${ALLOWED_HOSTS}
15
+ CSRF_TRUSTED_ORIGINS: ${CSRF_TRUSTED_ORIGINS}
16
+ WAIT_FOR_DB: "[% if use_postgres or use_mysql %]true[% else %]false[% endif %]"
17
+ DB_BACKEND: "[[ database ]]"
18
+ RUN_MIGRATIONS: ${RUN_MIGRATIONS:-true}
19
+ COLLECT_STATIC: ${COLLECT_STATIC:-true}
20
+ [% if use_postgres %]
21
+ DATABASE_URL: postgres://${POSTGRES_USER:-[[ project_slug ]]}:${POSTGRES_PASSWORD:-change-me}@db:${POSTGRES_PORT:-5432}/${POSTGRES_DB:-[[ project_slug ]]}
22
+ POSTGRES_HOST: db
23
+ POSTGRES_PORT: ${POSTGRES_PORT:-5432}
24
+ [% elif use_mysql %]
25
+ DATABASE_URL: mysql://${MYSQL_USER:-[[ project_slug ]]}:${MYSQL_PASSWORD:-change-me}@db:${MYSQL_PORT:-3306}/${MYSQL_DATABASE:-[[ project_slug ]]}
26
+ MYSQL_HOST: db
27
+ MYSQL_PORT: ${MYSQL_PORT:-3306}
28
+ [% else %]
29
+ DATABASE_URL: sqlite:////app/db.sqlite3
30
+ [% endif %]
31
+ [% if use_celery %]
32
+ REDIS_URL: redis://redis:6379/0
33
+ CELERY_BROKER_URL: redis://redis:6379/0
34
+ CELERY_RESULT_BACKEND: redis://redis:6379/1
35
+ [% endif %]
36
+ volumes:
37
+ - static_data:/app/static_root
38
+ - media_data:/app/media
39
+ [% if use_postgres or use_mysql or use_celery %]
40
+ depends_on:
41
+ [% if use_postgres or use_mysql %]
42
+ db:
43
+ condition: service_healthy
44
+ [% endif %]
45
+ [% if use_celery %]
46
+ redis:
47
+ condition: service_healthy
48
+ [% endif %]
49
+ [% endif %]
50
+ ports:
51
+ - "${APP_PORT:-8000}:8000"
52
+ restart: unless-stopped
53
+
54
+ [% if use_postgres %]
55
+ db:
56
+ image: postgres:16-alpine
57
+ env_file:
58
+ - .env
59
+ environment:
60
+ POSTGRES_DB: ${POSTGRES_DB:-[[ project_slug ]]}
61
+ POSTGRES_USER: ${POSTGRES_USER:-[[ project_slug ]]}
62
+ POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-change-me}
63
+ volumes:
64
+ - postgres_data:/var/lib/postgresql/data
65
+ healthcheck:
66
+ test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-[[ project_slug ]]} -d ${POSTGRES_DB:-[[ project_slug ]]}"]
67
+ interval: 10s
68
+ timeout: 5s
69
+ retries: 20
70
+ start_period: 10s
71
+ restart: unless-stopped
72
+ [% elif use_mysql %]
73
+ db:
74
+ image: mysql:8.4
75
+ env_file:
76
+ - .env
77
+ environment:
78
+ MYSQL_DATABASE: ${MYSQL_DATABASE:-[[ project_slug ]]}
79
+ MYSQL_USER: ${MYSQL_USER:-[[ project_slug ]]}
80
+ MYSQL_PASSWORD: ${MYSQL_PASSWORD:-change-me}
81
+ MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-root-change-me}
82
+ volumes:
83
+ - mysql_data:/var/lib/mysql
84
+ healthcheck:
85
+ test: ["CMD-SHELL", "mysqladmin ping -h localhost -u${MYSQL_USER:-[[ project_slug ]]} -p${MYSQL_PASSWORD:-change-me}"]
86
+ interval: 10s
87
+ timeout: 5s
88
+ retries: 30
89
+ start_period: 20s
90
+ restart: unless-stopped
91
+ [% endif %]
92
+ [% if use_celery %]
93
+ redis:
94
+ image: redis:7-alpine
95
+ command: redis-server --appendonly yes
96
+ volumes:
97
+ - redis_data:/data
98
+ healthcheck:
99
+ test: ["CMD", "redis-cli", "ping"]
100
+ interval: 10s
101
+ timeout: 5s
102
+ retries: 20
103
+ start_period: 5s
104
+ restart: unless-stopped
105
+
106
+ celery:
107
+ build:
108
+ context: .
109
+ dockerfile: Dockerfile
110
+ command: celery -A core worker -l info
111
+ env_file:
112
+ - .env
113
+ environment:
114
+ CODEFORTIFY_ENVIRONMENT: production
115
+ DEBUG: "False"
116
+ DB_BACKEND: "[[ database ]]"
117
+ WAIT_FOR_DB: "[% if use_postgres or use_mysql %]true[% else %]false[% endif %]"
118
+ RUN_MIGRATIONS: "false"
119
+ COLLECT_STATIC: "false"
120
+ [% if use_postgres %]
121
+ DATABASE_URL: postgres://${POSTGRES_USER:-[[ project_slug ]]}:${POSTGRES_PASSWORD:-change-me}@db:${POSTGRES_PORT:-5432}/${POSTGRES_DB:-[[ project_slug ]]}
122
+ POSTGRES_HOST: db
123
+ POSTGRES_PORT: ${POSTGRES_PORT:-5432}
124
+ [% elif use_mysql %]
125
+ DATABASE_URL: mysql://${MYSQL_USER:-[[ project_slug ]]}:${MYSQL_PASSWORD:-change-me}@db:${MYSQL_PORT:-3306}/${MYSQL_DATABASE:-[[ project_slug ]]}
126
+ MYSQL_HOST: db
127
+ MYSQL_PORT: ${MYSQL_PORT:-3306}
128
+ [% else %]
129
+ DATABASE_URL: sqlite:////app/db.sqlite3
130
+ [% endif %]
131
+ REDIS_URL: redis://redis:6379/0
132
+ CELERY_BROKER_URL: redis://redis:6379/0
133
+ CELERY_RESULT_BACKEND: redis://redis:6379/1
134
+ [% if use_postgres or use_mysql %]
135
+ depends_on:
136
+ db:
137
+ condition: service_healthy
138
+ redis:
139
+ condition: service_healthy
140
+ [% else %]
141
+ depends_on:
142
+ redis:
143
+ condition: service_healthy
144
+ [% endif %]
145
+ restart: unless-stopped
146
+ [% endif %]
147
+
148
+ volumes:
149
+ [% if use_postgres %]
150
+ postgres_data:
151
+ [% elif use_mysql %]
152
+ mysql_data:
153
+ [% endif %]
154
+ [% if use_celery %]
155
+ redis_data:
156
+ [% endif %]
157
+ static_data:
158
+ media_data:
@@ -0,0 +1,208 @@
1
+ name: [[ project_slug ]]
2
+
3
+ services:
4
+ web:
5
+ build:
6
+ context: .
7
+ dockerfile: Dockerfile
8
+ command: python manage.py runserver 0.0.0.0:8000
9
+ env_file:
10
+ - .env
11
+ environment:
12
+ CODEFORTIFY_ENVIRONMENT: ${CODEFORTIFY_ENVIRONMENT:-dev}
13
+ DEBUG: ${DEBUG:-True}
14
+ ALLOWED_HOSTS: ${ALLOWED_HOSTS:-localhost,127.0.0.1,0.0.0.0}
15
+ CSRF_TRUSTED_ORIGINS: ${CSRF_TRUSTED_ORIGINS:-http://localhost:8000,http://127.0.0.1:8000}
16
+ WAIT_FOR_DB: "[% if use_postgres or use_mysql %]true[% else %]false[% endif %]"
17
+ DB_BACKEND: "[[ database ]]"
18
+ RUN_MIGRATIONS: "true"
19
+ COLLECT_STATIC: "false"
20
+ [% if use_postgres %]
21
+ DATABASE_URL: postgres://${POSTGRES_USER:-[[ project_slug ]]}:${POSTGRES_PASSWORD:-change-me}@db:${POSTGRES_PORT:-5432}/${POSTGRES_DB:-[[ project_slug ]]}
22
+ POSTGRES_HOST: db
23
+ POSTGRES_PORT: ${POSTGRES_PORT:-5432}
24
+ [% elif use_mysql %]
25
+ DATABASE_URL: mysql://${MYSQL_USER:-[[ project_slug ]]}:${MYSQL_PASSWORD:-change-me}@db:${MYSQL_PORT:-3306}/${MYSQL_DATABASE:-[[ project_slug ]]}
26
+ MYSQL_HOST: db
27
+ MYSQL_PORT: ${MYSQL_PORT:-3306}
28
+ [% else %]
29
+ DATABASE_URL: sqlite:////app/db.sqlite3
30
+ [% endif %]
31
+ [% if use_celery %]
32
+ REDIS_URL: redis://redis:6379/0
33
+ CELERY_BROKER_URL: redis://redis:6379/0
34
+ CELERY_RESULT_BACKEND: redis://redis:6379/1
35
+ [% endif %]
36
+ volumes:
37
+ - .:/app
38
+ - static_data:/app/static_root
39
+ - media_data:/app/media
40
+ [% if use_postgres or use_mysql or use_celery %]
41
+ depends_on:
42
+ [% if use_postgres or use_mysql %]
43
+ db:
44
+ condition: service_healthy
45
+ [% endif %]
46
+ [% if use_celery %]
47
+ redis:
48
+ condition: service_healthy
49
+ [% endif %]
50
+ [% endif %]
51
+ ports:
52
+ - "${APP_PORT:-8000}:8000"
53
+ restart: unless-stopped
54
+
55
+ [% if use_postgres %]
56
+ db:
57
+ image: postgres:16-alpine
58
+ env_file:
59
+ - .env
60
+ environment:
61
+ POSTGRES_DB: ${POSTGRES_DB:-[[ project_slug ]]}
62
+ POSTGRES_USER: ${POSTGRES_USER:-[[ project_slug ]]}
63
+ POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-change-me}
64
+ volumes:
65
+ - postgres_data:/var/lib/postgresql/data
66
+ healthcheck:
67
+ test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-[[ project_slug ]]} -d ${POSTGRES_DB:-[[ project_slug ]]}"]
68
+ interval: 5s
69
+ timeout: 5s
70
+ retries: 20
71
+ start_period: 5s
72
+ restart: unless-stopped
73
+ [% elif use_mysql %]
74
+ db:
75
+ image: mysql:8.4
76
+ env_file:
77
+ - .env
78
+ environment:
79
+ MYSQL_DATABASE: ${MYSQL_DATABASE:-[[ project_slug ]]}
80
+ MYSQL_USER: ${MYSQL_USER:-[[ project_slug ]]}
81
+ MYSQL_PASSWORD: ${MYSQL_PASSWORD:-change-me}
82
+ MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-root-change-me}
83
+ volumes:
84
+ - mysql_data:/var/lib/mysql
85
+ healthcheck:
86
+ test: ["CMD-SHELL", "mysqladmin ping -h localhost -u${MYSQL_USER:-[[ project_slug ]]} -p${MYSQL_PASSWORD:-change-me}"]
87
+ interval: 10s
88
+ timeout: 5s
89
+ retries: 30
90
+ start_period: 20s
91
+ restart: unless-stopped
92
+ [% endif %]
93
+ [% if use_celery %]
94
+ redis:
95
+ image: redis:7-alpine
96
+ command: redis-server --appendonly yes
97
+ volumes:
98
+ - redis_data:/data
99
+ healthcheck:
100
+ test: ["CMD", "redis-cli", "ping"]
101
+ interval: 5s
102
+ timeout: 5s
103
+ retries: 20
104
+ start_period: 5s
105
+ restart: unless-stopped
106
+
107
+ celery:
108
+ build:
109
+ context: .
110
+ dockerfile: Dockerfile
111
+ command: celery -A core worker -l info
112
+ env_file:
113
+ - .env
114
+ environment:
115
+ CODEFORTIFY_ENVIRONMENT: ${CODEFORTIFY_ENVIRONMENT:-dev}
116
+ DEBUG: ${DEBUG:-True}
117
+ DB_BACKEND: "[[ database ]]"
118
+ WAIT_FOR_DB: "[% if use_postgres or use_mysql %]true[% else %]false[% endif %]"
119
+ RUN_MIGRATIONS: "false"
120
+ COLLECT_STATIC: "false"
121
+ [% if use_postgres %]
122
+ DATABASE_URL: postgres://${POSTGRES_USER:-[[ project_slug ]]}:${POSTGRES_PASSWORD:-change-me}@db:${POSTGRES_PORT:-5432}/${POSTGRES_DB:-[[ project_slug ]]}
123
+ POSTGRES_HOST: db
124
+ POSTGRES_PORT: ${POSTGRES_PORT:-5432}
125
+ [% elif use_mysql %]
126
+ DATABASE_URL: mysql://${MYSQL_USER:-[[ project_slug ]]}:${MYSQL_PASSWORD:-change-me}@db:${MYSQL_PORT:-3306}/${MYSQL_DATABASE:-[[ project_slug ]]}
127
+ MYSQL_HOST: db
128
+ MYSQL_PORT: ${MYSQL_PORT:-3306}
129
+ [% else %]
130
+ DATABASE_URL: sqlite:////app/db.sqlite3
131
+ [% endif %]
132
+ REDIS_URL: redis://redis:6379/0
133
+ CELERY_BROKER_URL: redis://redis:6379/0
134
+ CELERY_RESULT_BACKEND: redis://redis:6379/1
135
+ volumes:
136
+ - .:/app
137
+ [% if use_postgres or use_mysql %]
138
+ depends_on:
139
+ db:
140
+ condition: service_healthy
141
+ redis:
142
+ condition: service_healthy
143
+ [% else %]
144
+ depends_on:
145
+ redis:
146
+ condition: service_healthy
147
+ [% endif %]
148
+ restart: unless-stopped
149
+
150
+ celery-beat:
151
+ build:
152
+ context: .
153
+ dockerfile: Dockerfile
154
+ command: celery -A core beat -l info --scheduler celery.beat:PersistentScheduler --pidfile=
155
+ profiles:
156
+ - beat
157
+ env_file:
158
+ - .env
159
+ environment:
160
+ CODEFORTIFY_ENVIRONMENT: ${CODEFORTIFY_ENVIRONMENT:-dev}
161
+ DEBUG: ${DEBUG:-True}
162
+ DB_BACKEND: "[[ database ]]"
163
+ WAIT_FOR_DB: "[% if use_postgres or use_mysql %]true[% else %]false[% endif %]"
164
+ RUN_MIGRATIONS: "false"
165
+ COLLECT_STATIC: "false"
166
+ [% if use_postgres %]
167
+ DATABASE_URL: postgres://${POSTGRES_USER:-[[ project_slug ]]}:${POSTGRES_PASSWORD:-change-me}@db:${POSTGRES_PORT:-5432}/${POSTGRES_DB:-[[ project_slug ]]}
168
+ POSTGRES_HOST: db
169
+ POSTGRES_PORT: ${POSTGRES_PORT:-5432}
170
+ [% elif use_mysql %]
171
+ DATABASE_URL: mysql://${MYSQL_USER:-[[ project_slug ]]}:${MYSQL_PASSWORD:-change-me}@db:${MYSQL_PORT:-3306}/${MYSQL_DATABASE:-[[ project_slug ]]}
172
+ MYSQL_HOST: db
173
+ MYSQL_PORT: ${MYSQL_PORT:-3306}
174
+ [% else %]
175
+ DATABASE_URL: sqlite:////app/db.sqlite3
176
+ [% endif %]
177
+ REDIS_URL: redis://redis:6379/0
178
+ CELERY_BROKER_URL: redis://redis:6379/0
179
+ CELERY_RESULT_BACKEND: redis://redis:6379/1
180
+ volumes:
181
+ - .:/app
182
+ - celery_beat_data:/app/.celerybeat
183
+ [% if use_postgres or use_mysql %]
184
+ depends_on:
185
+ db:
186
+ condition: service_healthy
187
+ redis:
188
+ condition: service_healthy
189
+ [% else %]
190
+ depends_on:
191
+ redis:
192
+ condition: service_healthy
193
+ [% endif %]
194
+ restart: unless-stopped
195
+ [% endif %]
196
+
197
+ volumes:
198
+ [% if use_postgres %]
199
+ postgres_data:
200
+ [% elif use_mysql %]
201
+ mysql_data:
202
+ [% endif %]
203
+ [% if use_celery %]
204
+ redis_data:
205
+ celery_beat_data:
206
+ [% endif %]
207
+ static_data:
208
+ media_data:
@@ -0,0 +1,132 @@
1
+ #!/usr/bin/env bash
2
+ set -Eeuo pipefail
3
+
4
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
+ PROJECT_ROOT="${SCRIPT_DIR}"
6
+ ENV_FILE="${PROJECT_ROOT}/.env"
7
+ ENV_EXAMPLE="${PROJECT_ROOT}/.env.example"
8
+
9
+ COMPOSE_FILE="docker-compose.yml"
10
+ if [ "${1:-}" = "--prod" ]; then
11
+ COMPOSE_FILE="docker-compose.prod.yml"
12
+ shift
13
+ fi
14
+
15
+ ACTION="${1:-up}"
16
+ LOG_SERVICE="${2:-web}"
17
+
18
+ log() {
19
+ printf '[docker_deploy] %s\n' "$*"
20
+ }
21
+
22
+ die() {
23
+ printf '[docker_deploy][error] %s\n' "$*" >&2
24
+ exit 1
25
+ }
26
+
27
+ usage() {
28
+ cat <<EOF
29
+ Usage:
30
+ ./docker_deploy.sh [--prod] [action] [service]
31
+
32
+ Actions:
33
+ up Build and start services, then run migrate/check
34
+ down Stop and remove services
35
+ restart Rebuild and restart services
36
+ ps Show container status
37
+ logs [svc] Tail logs (default service: web)
38
+ migrate Run Django migrations in web
39
+ check Run Django checks in web
40
+ test Run Django tests in web
41
+ shell Open shell in web container
42
+ EOF
43
+ }
44
+
45
+ ensure_docker() {
46
+ command -v docker >/dev/null 2>&1 || die "Docker is not installed."
47
+ docker info >/dev/null 2>&1 || die "Docker daemon is not reachable."
48
+ docker compose version >/dev/null 2>&1 || die "Docker Compose v2 is required."
49
+ }
50
+
51
+ ensure_env_file() {
52
+ if [ ! -f "${ENV_FILE}" ]; then
53
+ [ -f "${ENV_EXAMPLE}" ] || die ".env is missing and .env.example was not found."
54
+ cp "${ENV_EXAMPLE}" "${ENV_FILE}"
55
+ log "Created .env from .env.example"
56
+ fi
57
+ }
58
+
59
+ compose() {
60
+ docker compose --env-file "${ENV_FILE}" -f "${PROJECT_ROOT}/${COMPOSE_FILE}" "$@"
61
+ }
62
+
63
+ start_services() {
64
+ log "Building images with ${COMPOSE_FILE}"
65
+ compose build
66
+
67
+ [% if use_postgres or use_mysql %]
68
+ log "Starting database"
69
+ compose up -d db
70
+ [% endif %]
71
+ [% if use_celery %]
72
+ log "Starting redis"
73
+ compose up -d redis
74
+ [% endif %]
75
+
76
+ log "Starting web"
77
+ compose up -d web
78
+ [% if use_celery %]
79
+ log "Starting celery worker"
80
+ compose up -d celery
81
+ [% endif %]
82
+
83
+ log "Running migrations"
84
+ compose exec -T web python manage.py migrate --noinput
85
+
86
+ log "Running Django checks"
87
+ compose exec -T web python manage.py check
88
+
89
+ log "Service status"
90
+ compose ps
91
+ }
92
+
93
+ ensure_docker
94
+ ensure_env_file
95
+
96
+ case "${ACTION}" in
97
+ up)
98
+ start_services
99
+ ;;
100
+ down)
101
+ compose down
102
+ ;;
103
+ restart)
104
+ compose down
105
+ start_services
106
+ ;;
107
+ ps)
108
+ compose ps
109
+ ;;
110
+ logs)
111
+ compose logs -f "${LOG_SERVICE}"
112
+ ;;
113
+ migrate)
114
+ compose exec -T web python manage.py migrate --noinput
115
+ ;;
116
+ check)
117
+ compose exec -T web python manage.py check
118
+ ;;
119
+ test)
120
+ compose exec -T web python manage.py test
121
+ ;;
122
+ shell)
123
+ compose exec web bash
124
+ ;;
125
+ -h|--help|help)
126
+ usage
127
+ ;;
128
+ *)
129
+ usage
130
+ die "Unknown action: ${ACTION}"
131
+ ;;
132
+ esac
@@ -0,0 +1,12 @@
1
+ .git
2
+ .venv
3
+ venv
4
+ __pycache__
5
+ *.pyc
6
+ .pytest_cache
7
+ .mypy_cache
8
+ media
9
+ static_root
10
+ dist
11
+ build
12
+
@@ -0,0 +1,95 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ log() {
5
+ printf '[entrypoint] %s\n' "$*"
6
+ }
7
+
8
+ is_true() {
9
+ case "${1:-}" in
10
+ 1|true|TRUE|True|yes|YES|on|ON) return 0 ;;
11
+ *) return 1 ;;
12
+ esac
13
+ }
14
+
15
+ wait_for_database() {
16
+ python - <<'PY'
17
+ import os
18
+ import time
19
+ import sys
20
+
21
+ backend = (os.environ.get("DB_BACKEND") or "").strip().lower()
22
+ timeout = int(os.environ.get("DB_WAIT_TIMEOUT", "120"))
23
+
24
+ if backend in {"", "sqlite"}:
25
+ raise SystemExit(0)
26
+
27
+ started = time.time()
28
+ last_error = "unknown error"
29
+
30
+ while True:
31
+ try:
32
+ if backend == "postgres":
33
+ import psycopg2
34
+
35
+ dsn = (os.environ.get("DATABASE_URL") or "").strip()
36
+ if dsn:
37
+ conn = psycopg2.connect(dsn)
38
+ else:
39
+ conn = psycopg2.connect(
40
+ host=os.environ.get("POSTGRES_HOST", "db"),
41
+ port=int(os.environ.get("POSTGRES_PORT", "5432")),
42
+ user=os.environ.get("POSTGRES_USER", "postgres"),
43
+ password=os.environ.get("POSTGRES_PASSWORD", ""),
44
+ dbname=os.environ.get("POSTGRES_DB", "postgres"),
45
+ )
46
+ conn.close()
47
+ raise SystemExit(0)
48
+
49
+ if backend == "mysql":
50
+ import pymysql
51
+
52
+ conn = pymysql.connect(
53
+ host=os.environ.get("MYSQL_HOST", "db"),
54
+ port=int(os.environ.get("MYSQL_PORT", "3306")),
55
+ user=os.environ.get("MYSQL_USER", "root"),
56
+ password=os.environ.get("MYSQL_PASSWORD", ""),
57
+ database=os.environ.get("MYSQL_DATABASE", ""),
58
+ connect_timeout=5,
59
+ )
60
+ conn.close()
61
+ raise SystemExit(0)
62
+
63
+ raise SystemExit(0)
64
+ except SystemExit:
65
+ raise
66
+ except Exception as exc:
67
+ last_error = str(exc)
68
+ if time.time() - started > timeout:
69
+ print(f"database did not become ready within {timeout}s: {last_error}", file=sys.stderr)
70
+ raise SystemExit(1)
71
+ time.sleep(1)
72
+ PY
73
+ }
74
+
75
+ mkdir -p /app/static_root /app/media
76
+
77
+ if is_true "${WAIT_FOR_DB:-true}"; then
78
+ log "Waiting for database backend ${DB_BACKEND:-sqlite}"
79
+ wait_for_database
80
+ log "Database is ready"
81
+ fi
82
+
83
+ if is_true "${RUN_MIGRATIONS:-true}"; then
84
+ log "Running migrations"
85
+ python manage.py migrate --noinput
86
+ fi
87
+
88
+ if is_true "${COLLECT_STATIC:-false}"; then
89
+ log "Collecting static files"
90
+ python manage.py collectstatic --noinput
91
+ fi
92
+
93
+ log "Starting: $*"
94
+ exec "$@"
95
+
@@ -0,0 +1,7 @@
1
+ from django.apps import AppConfig
2
+
3
+
4
+ class ApiConfig(AppConfig):
5
+ default_auto_field = "django.db.models.BigAutoField"
6
+ name = "apps.api"
7
+
@@ -0,0 +1,7 @@
1
+ from rest_framework import serializers
2
+
3
+
4
+ class HealthSerializer(serializers.Serializer):
5
+ status = serializers.CharField()
6
+ service = serializers.CharField()
7
+
@@ -0,0 +1,11 @@
1
+ from django.urls import reverse
2
+ from rest_framework import status
3
+ from rest_framework.test import APITestCase
4
+
5
+
6
+ class HealthApiTests(APITestCase):
7
+ def test_health_endpoint(self):
8
+ response = self.client.get(reverse("api:health"))
9
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
10
+ self.assertEqual(response.data["status"], "ok")
11
+
@@ -0,0 +1,11 @@
1
+ from django.urls import path
2
+
3
+ from apps.api.views import HealthAPIView
4
+
5
+
6
+ app_name = "api"
7
+
8
+ urlpatterns = [
9
+ path("health/", HealthAPIView.as_view(), name="health"),
10
+ ]
11
+
@@ -0,0 +1,16 @@
1
+ from rest_framework.permissions import AllowAny
2
+ from rest_framework.response import Response
3
+ from rest_framework.views import APIView
4
+
5
+ from apps.api.serializers import HealthSerializer
6
+
7
+
8
+ class HealthAPIView(APIView):
9
+ authentication_classes = []
10
+ permission_classes = [AllowAny]
11
+
12
+ def get(self, request):
13
+ serializer = HealthSerializer(data={"status": "ok", "service": "api"})
14
+ serializer.is_valid(raise_exception=True)
15
+ return Response(serializer.validated_data)
16
+
@@ -0,0 +1,5 @@
1
+ <div>
2
+ <p>HTMX is active in this starter.</p>
3
+ <small>Server-rendered fragment updated at {% now "Y-m-d H:i:s" %}</small>
4
+ </div>
5
+