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.
- codefortify_starter/__init__.py +7 -0
- codefortify_starter/__main__.py +6 -0
- codefortify_starter/cli.py +103 -0
- codefortify_starter/constants.py +48 -0
- codefortify_starter/generator.py +246 -0
- codefortify_starter/template_engine.py +63 -0
- codefortify_starter/templates/base_project/README.md.jinja +87 -0
- codefortify_starter/templates/base_project/apps/__init__.py +1 -0
- codefortify_starter/templates/base_project/apps/home/__init__.py +1 -0
- codefortify_starter/templates/base_project/apps/home/apps.py.jinja +7 -0
- codefortify_starter/templates/base_project/apps/home/templates/home/index.html.jinja +25 -0
- codefortify_starter/templates/base_project/apps/home/tests.py.jinja +17 -0
- codefortify_starter/templates/base_project/apps/home/urls.py.jinja +14 -0
- codefortify_starter/templates/base_project/apps/home/views.py.jinja +13 -0
- codefortify_starter/templates/base_project/core/__init__.py.jinja +7 -0
- codefortify_starter/templates/base_project/core/asgi.py.jinja +10 -0
- codefortify_starter/templates/base_project/core/env.py.jinja +46 -0
- codefortify_starter/templates/base_project/core/middleware/__init__.py +1 -0
- codefortify_starter/templates/base_project/core/middleware/exceptions.py +26 -0
- codefortify_starter/templates/base_project/core/middleware/security.py +12 -0
- codefortify_starter/templates/base_project/core/settings/__init__.py +1 -0
- codefortify_starter/templates/base_project/core/settings/base.py.jinja +160 -0
- codefortify_starter/templates/base_project/core/settings/dev.py.jinja +8 -0
- codefortify_starter/templates/base_project/core/settings/production.py.jinja +21 -0
- codefortify_starter/templates/base_project/core/templates/errors/400.html +8 -0
- codefortify_starter/templates/base_project/core/templates/errors/403.html +8 -0
- codefortify_starter/templates/base_project/core/templates/errors/404.html +8 -0
- codefortify_starter/templates/base_project/core/templates/errors/405.html +8 -0
- codefortify_starter/templates/base_project/core/templates/errors/500.html +8 -0
- codefortify_starter/templates/base_project/core/urls.py.jinja +23 -0
- codefortify_starter/templates/base_project/core/views.py.jinja +56 -0
- codefortify_starter/templates/base_project/core/wsgi.py.jinja +10 -0
- codefortify_starter/templates/base_project/dot-env.example.jinja +66 -0
- codefortify_starter/templates/base_project/dot-gitignore +34 -0
- codefortify_starter/templates/base_project/manage.py.jinja +16 -0
- codefortify_starter/templates/base_project/static/css/main.css +19 -0
- codefortify_starter/templates/base_project/templates/base.html.jinja +19 -0
- codefortify_starter/templates/features/celery/apps/home/tasks.py.jinja +7 -0
- codefortify_starter/templates/features/celery/core/celery.py.jinja +16 -0
- codefortify_starter/templates/features/docker/DOCKER_README.md.jinja +67 -0
- codefortify_starter/templates/features/docker/Dockerfile.jinja +20 -0
- codefortify_starter/templates/features/docker/docker-compose.prod.yml.jinja +158 -0
- codefortify_starter/templates/features/docker/docker-compose.yml.jinja +208 -0
- codefortify_starter/templates/features/docker/docker_deploy.sh.jinja +132 -0
- codefortify_starter/templates/features/docker/dot-dockerignore +12 -0
- codefortify_starter/templates/features/docker/entrypoint.sh.jinja +95 -0
- codefortify_starter/templates/features/drf/apps/api/__init__.py +1 -0
- codefortify_starter/templates/features/drf/apps/api/apps.py.jinja +7 -0
- codefortify_starter/templates/features/drf/apps/api/serializers.py +7 -0
- codefortify_starter/templates/features/drf/apps/api/tests.py +11 -0
- codefortify_starter/templates/features/drf/apps/api/urls.py +11 -0
- codefortify_starter/templates/features/drf/apps/api/views.py +16 -0
- codefortify_starter/templates/features/htmx/templates/partials/example.html.jinja +5 -0
- codefortify_starter/validators.py +70 -0
- codefortify_starter-1.0.0.dist-info/METADATA +276 -0
- codefortify_starter-1.0.0.dist-info/RECORD +60 -0
- codefortify_starter-1.0.0.dist-info/WHEEL +5 -0
- codefortify_starter-1.0.0.dist-info/entry_points.txt +2 -0
- codefortify_starter-1.0.0.dist-info/licenses/LICENSE +22 -0
- codefortify_starter-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
ROOT_DIR = Path(__file__).resolve().parent.parent
|
|
6
|
+
DOTENV_FILE = ROOT_DIR / ".env"
|
|
7
|
+
DOTENV_LOCAL_FILE = ROOT_DIR / ".env.local"
|
|
8
|
+
TRUE_VALUES = {"1", "true", "yes", "on"}
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def env_flag(name: str, default: bool = False) -> bool:
|
|
12
|
+
value = os.environ.get(name)
|
|
13
|
+
if value is None:
|
|
14
|
+
return default
|
|
15
|
+
return value.strip().lower() in TRUE_VALUES
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _load_env_file(path: Path) -> None:
|
|
19
|
+
if not path.exists():
|
|
20
|
+
return
|
|
21
|
+
for raw_line in path.read_text(encoding="utf-8").splitlines():
|
|
22
|
+
line = raw_line.strip()
|
|
23
|
+
if not line or line.startswith("#") or "=" not in line:
|
|
24
|
+
continue
|
|
25
|
+
key, value = line.split("=", 1)
|
|
26
|
+
key = key.strip()
|
|
27
|
+
value = value.strip().strip('"').strip("'")
|
|
28
|
+
if key and key not in os.environ:
|
|
29
|
+
os.environ[key] = value
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def configure_environment() -> str:
|
|
33
|
+
explicit_environment = (os.environ.get("CODEFORTIFY_ENVIRONMENT") or "").strip().lower()
|
|
34
|
+
load_dotenv_in_production = env_flag("CODEFORTIFY_LOAD_DOTENV_IN_PRODUCTION", default=False)
|
|
35
|
+
should_load_dotenv = explicit_environment != "production" or load_dotenv_in_production
|
|
36
|
+
|
|
37
|
+
if should_load_dotenv:
|
|
38
|
+
if DOTENV_LOCAL_FILE.exists() and not env_flag("CODEFORTIFY_SKIP_DOTENV_LOCAL", default=False):
|
|
39
|
+
_load_env_file(DOTENV_LOCAL_FILE)
|
|
40
|
+
_load_env_file(DOTENV_FILE)
|
|
41
|
+
|
|
42
|
+
environment = (os.environ.get("CODEFORTIFY_ENVIRONMENT") or "dev").strip().lower()
|
|
43
|
+
settings_module = "core.settings.production" if environment == "production" else "core.settings.dev"
|
|
44
|
+
os.environ.setdefault("DJANGO_SETTINGS_MODULE", settings_module)
|
|
45
|
+
return settings_module
|
|
46
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from django.conf import settings
|
|
4
|
+
from django.http import JsonResponse
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class SafeExceptionMiddleware:
|
|
11
|
+
def __init__(self, get_response):
|
|
12
|
+
self.get_response = get_response
|
|
13
|
+
|
|
14
|
+
def __call__(self, request):
|
|
15
|
+
try:
|
|
16
|
+
return self.get_response(request)
|
|
17
|
+
except Exception as exc: # pragma: no cover
|
|
18
|
+
logger.exception("Unhandled application exception")
|
|
19
|
+
if settings.DEBUG:
|
|
20
|
+
raise
|
|
21
|
+
|
|
22
|
+
accept_header = request.headers.get("Accept", "")
|
|
23
|
+
if "application/json" in accept_header or request.path.startswith("/api/"):
|
|
24
|
+
return JsonResponse({"detail": str(exc) or "Internal server error."}, status=500)
|
|
25
|
+
raise
|
|
26
|
+
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
class SecurityHeadersMiddleware:
|
|
2
|
+
def __init__(self, get_response):
|
|
3
|
+
self.get_response = get_response
|
|
4
|
+
|
|
5
|
+
def __call__(self, request):
|
|
6
|
+
response = self.get_response(request)
|
|
7
|
+
response.setdefault("Referrer-Policy", "strict-origin-when-cross-origin")
|
|
8
|
+
response.setdefault("X-Content-Type-Options", "nosniff")
|
|
9
|
+
response.setdefault("X-Frame-Options", "DENY")
|
|
10
|
+
response.setdefault("Permissions-Policy", "geolocation=(), microphone=(), camera=()")
|
|
11
|
+
return response
|
|
12
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from decouple import Csv, config
|
|
4
|
+
from django.core.exceptions import ImproperlyConfigured
|
|
5
|
+
[% if use_postgres or use_mysql %]
|
|
6
|
+
import dj_database_url
|
|
7
|
+
[% endif %]
|
|
8
|
+
[% if use_mysql %]
|
|
9
|
+
import pymysql
|
|
10
|
+
|
|
11
|
+
pymysql.install_as_MySQLdb()
|
|
12
|
+
[% endif %]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
BASE_DIR = Path(__file__).resolve().parent.parent.parent
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
DEBUG = config("DEBUG", default=True, cast=bool)
|
|
19
|
+
SECRET_KEY = config("SECRET_KEY", default="django-insecure-change-me")
|
|
20
|
+
if not DEBUG and SECRET_KEY == "django-insecure-change-me":
|
|
21
|
+
raise ImproperlyConfigured("SECRET_KEY must be set when DEBUG is False.")
|
|
22
|
+
|
|
23
|
+
ALLOWED_HOSTS = config("ALLOWED_HOSTS", default="localhost,127.0.0.1,0.0.0.0", cast=Csv())
|
|
24
|
+
CSRF_TRUSTED_ORIGINS = config(
|
|
25
|
+
"CSRF_TRUSTED_ORIGINS",
|
|
26
|
+
default="http://localhost:8000,http://127.0.0.1:8000",
|
|
27
|
+
cast=Csv(),
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
INSTALLED_APPS = [
|
|
31
|
+
"django.contrib.admin",
|
|
32
|
+
"django.contrib.auth",
|
|
33
|
+
"django.contrib.contenttypes",
|
|
34
|
+
"django.contrib.sessions",
|
|
35
|
+
"django.contrib.messages",
|
|
36
|
+
"django.contrib.staticfiles",
|
|
37
|
+
[% if use_htmx %]
|
|
38
|
+
"django_htmx",
|
|
39
|
+
[% endif %]
|
|
40
|
+
[% if use_drf %]
|
|
41
|
+
"rest_framework",
|
|
42
|
+
"django_filters",
|
|
43
|
+
"apps.api",
|
|
44
|
+
[% endif %]
|
|
45
|
+
"apps.home",
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
MIDDLEWARE = [
|
|
49
|
+
"core.middleware.security.SecurityHeadersMiddleware",
|
|
50
|
+
"django.middleware.security.SecurityMiddleware",
|
|
51
|
+
[% if use_docker %]
|
|
52
|
+
"whitenoise.middleware.WhiteNoiseMiddleware",
|
|
53
|
+
[% endif %]
|
|
54
|
+
"django.contrib.sessions.middleware.SessionMiddleware",
|
|
55
|
+
"django.middleware.common.CommonMiddleware",
|
|
56
|
+
[% if use_htmx %]
|
|
57
|
+
"django_htmx.middleware.HtmxMiddleware",
|
|
58
|
+
[% endif %]
|
|
59
|
+
"django.middleware.csrf.CsrfViewMiddleware",
|
|
60
|
+
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
|
61
|
+
"django.contrib.messages.middleware.MessageMiddleware",
|
|
62
|
+
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
|
63
|
+
"core.middleware.exceptions.SafeExceptionMiddleware",
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
ROOT_URLCONF = "core.urls"
|
|
67
|
+
WSGI_APPLICATION = "core.wsgi.application"
|
|
68
|
+
ASGI_APPLICATION = "core.asgi.application"
|
|
69
|
+
|
|
70
|
+
TEMPLATES = [
|
|
71
|
+
{
|
|
72
|
+
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
|
73
|
+
"DIRS": [BASE_DIR / "templates"],
|
|
74
|
+
"APP_DIRS": True,
|
|
75
|
+
"OPTIONS": {
|
|
76
|
+
"context_processors": [
|
|
77
|
+
"django.template.context_processors.request",
|
|
78
|
+
"django.contrib.auth.context_processors.auth",
|
|
79
|
+
"django.contrib.messages.context_processors.messages",
|
|
80
|
+
]
|
|
81
|
+
},
|
|
82
|
+
}
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
[% if use_sqlite %]
|
|
86
|
+
DATABASES = {
|
|
87
|
+
"default": {
|
|
88
|
+
"ENGINE": "django.db.backends.sqlite3",
|
|
89
|
+
"NAME": BASE_DIR / "db.sqlite3",
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
[% elif use_postgres or use_mysql %]
|
|
93
|
+
DATABASE_URL = config("DATABASE_URL", default="").strip()
|
|
94
|
+
|
|
95
|
+
if DATABASE_URL:
|
|
96
|
+
DATABASES = {
|
|
97
|
+
"default": dj_database_url.parse(
|
|
98
|
+
DATABASE_URL,
|
|
99
|
+
conn_max_age=config("DB_CONN_MAX_AGE", default=600, cast=int),
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
else:
|
|
103
|
+
DATABASES = {
|
|
104
|
+
"default": {
|
|
105
|
+
"ENGINE": "django.db.backends.sqlite3",
|
|
106
|
+
"NAME": BASE_DIR / "db.sqlite3",
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
[% endif %]
|
|
110
|
+
|
|
111
|
+
AUTH_PASSWORD_VALIDATORS = [
|
|
112
|
+
{"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator"},
|
|
113
|
+
{"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator"},
|
|
114
|
+
{"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"},
|
|
115
|
+
{"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"},
|
|
116
|
+
]
|
|
117
|
+
|
|
118
|
+
LANGUAGE_CODE = config("LANGUAGE_CODE", default="en-us")
|
|
119
|
+
TIME_ZONE = config("TIME_ZONE", default="UTC")
|
|
120
|
+
USE_I18N = True
|
|
121
|
+
USE_TZ = True
|
|
122
|
+
|
|
123
|
+
STATIC_URL = "/static/"
|
|
124
|
+
STATIC_ROOT = BASE_DIR / "static_root"
|
|
125
|
+
STATICFILES_DIRS = [BASE_DIR / "static"]
|
|
126
|
+
[% if use_docker %]
|
|
127
|
+
STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"
|
|
128
|
+
[% endif %]
|
|
129
|
+
|
|
130
|
+
MEDIA_URL = "/media/"
|
|
131
|
+
MEDIA_ROOT = BASE_DIR / "media"
|
|
132
|
+
|
|
133
|
+
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
|
134
|
+
|
|
135
|
+
[% if use_drf %]
|
|
136
|
+
REST_FRAMEWORK = {
|
|
137
|
+
"DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.AllowAny",),
|
|
138
|
+
"DEFAULT_AUTHENTICATION_CLASSES": ("rest_framework.authentication.SessionAuthentication",),
|
|
139
|
+
"DEFAULT_FILTER_BACKENDS": (
|
|
140
|
+
"django_filters.rest_framework.DjangoFilterBackend",
|
|
141
|
+
"rest_framework.filters.OrderingFilter",
|
|
142
|
+
"rest_framework.filters.SearchFilter",
|
|
143
|
+
),
|
|
144
|
+
}
|
|
145
|
+
[% endif %]
|
|
146
|
+
|
|
147
|
+
[% if use_celery %]
|
|
148
|
+
REDIS_URL = config("REDIS_URL", default="redis://127.0.0.1:6379/0")
|
|
149
|
+
CELERY_BROKER_URL = config("CELERY_BROKER_URL", default=REDIS_URL)
|
|
150
|
+
CELERY_RESULT_BACKEND = config("CELERY_RESULT_BACKEND", default="redis://127.0.0.1:6379/1")
|
|
151
|
+
CELERY_ACCEPT_CONTENT = ["json"]
|
|
152
|
+
CELERY_TASK_SERIALIZER = "json"
|
|
153
|
+
CELERY_RESULT_SERIALIZER = "json"
|
|
154
|
+
CELERY_TIMEZONE = TIME_ZONE
|
|
155
|
+
CELERY_ENABLE_UTC = USE_TZ
|
|
156
|
+
[% endif %]
|
|
157
|
+
|
|
158
|
+
SESSION_COOKIE_HTTPONLY = True
|
|
159
|
+
SESSION_COOKIE_SECURE = config("SESSION_COOKIE_SECURE", default=not DEBUG, cast=bool)
|
|
160
|
+
CSRF_COOKIE_SECURE = config("CSRF_COOKIE_SECURE", default=not DEBUG, cast=bool)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from decouple import config
|
|
2
|
+
from django.core.exceptions import ImproperlyConfigured
|
|
3
|
+
|
|
4
|
+
from .base import * # noqa: F403,F401
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
DEBUG = False
|
|
8
|
+
|
|
9
|
+
if SECRET_KEY == "django-insecure-change-me": # noqa: F405
|
|
10
|
+
raise ImproperlyConfigured("SECRET_KEY must be set in production.") # noqa: F405
|
|
11
|
+
|
|
12
|
+
if not ALLOWED_HOSTS: # noqa: F405
|
|
13
|
+
raise ImproperlyConfigured("ALLOWED_HOSTS must be configured in production.") # noqa: F405
|
|
14
|
+
|
|
15
|
+
SECURE_SSL_REDIRECT = config("SECURE_SSL_REDIRECT", default=True, cast=bool)
|
|
16
|
+
SESSION_COOKIE_SECURE = True
|
|
17
|
+
CSRF_COOKIE_SECURE = True
|
|
18
|
+
SECURE_HSTS_SECONDS = config("SECURE_HSTS_SECONDS", default=31536000, cast=int)
|
|
19
|
+
SECURE_HSTS_INCLUDE_SUBDOMAINS = config("SECURE_HSTS_INCLUDE_SUBDOMAINS", default=True, cast=bool)
|
|
20
|
+
SECURE_HSTS_PRELOAD = config("SECURE_HSTS_PRELOAD", default=True, cast=bool)
|
|
21
|
+
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from django.conf import settings
|
|
2
|
+
from django.conf.urls.static import static
|
|
3
|
+
from django.contrib import admin
|
|
4
|
+
from django.urls import include, path
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
urlpatterns = [
|
|
8
|
+
path("admin/", admin.site.urls),
|
|
9
|
+
path("", include(("apps.home.urls", "home"), namespace="home")),
|
|
10
|
+
[% if use_drf %]
|
|
11
|
+
path("api/", include(("apps.api.urls", "api"), namespace="api")),
|
|
12
|
+
[% endif %]
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
if settings.DEBUG:
|
|
16
|
+
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
|
17
|
+
|
|
18
|
+
handler400 = "core.views.bad_request"
|
|
19
|
+
handler403 = "core.views.permission_denied"
|
|
20
|
+
handler404 = "core.views.page_not_found"
|
|
21
|
+
handler405 = "core.views.method_not_allowed"
|
|
22
|
+
handler500 = "core.views.server_error"
|
|
23
|
+
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from django.http import JsonResponse
|
|
2
|
+
from django.shortcuts import render
|
|
3
|
+
from django.views.decorators.csrf import requires_csrf_token
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def _expects_json(request) -> bool:
|
|
7
|
+
accept_header = request.headers.get("Accept", "")
|
|
8
|
+
content_type = request.headers.get("Content-Type", "")
|
|
9
|
+
return (
|
|
10
|
+
"application/json" in accept_header
|
|
11
|
+
or "application/json" in content_type
|
|
12
|
+
or request.path.startswith("/api/")
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@requires_csrf_token
|
|
17
|
+
def bad_request(request, exception, template_name="errors/400.html"):
|
|
18
|
+
if _expects_json(request):
|
|
19
|
+
return JsonResponse({"detail": "Bad request."}, status=400)
|
|
20
|
+
return render(request, template_name, status=400)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@requires_csrf_token
|
|
24
|
+
def permission_denied(request, exception, template_name="errors/403.html"):
|
|
25
|
+
if _expects_json(request):
|
|
26
|
+
return JsonResponse({"detail": "Permission denied."}, status=403)
|
|
27
|
+
return render(request, template_name, status=403)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@requires_csrf_token
|
|
31
|
+
def page_not_found(request, exception, template_name="errors/404.html"):
|
|
32
|
+
if _expects_json(request):
|
|
33
|
+
return JsonResponse({"detail": "Not found."}, status=404)
|
|
34
|
+
return render(request, template_name, status=404)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@requires_csrf_token
|
|
38
|
+
def method_not_allowed(request, exception, template_name="errors/405.html"):
|
|
39
|
+
if _expects_json(request):
|
|
40
|
+
return JsonResponse({"detail": "Method not allowed."}, status=405)
|
|
41
|
+
return render(request, template_name, status=405)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@requires_csrf_token
|
|
45
|
+
def server_error(request, template_name="errors/500.html"):
|
|
46
|
+
if _expects_json(request):
|
|
47
|
+
return JsonResponse({"detail": "Internal server error."}, status=500)
|
|
48
|
+
return render(request, template_name, status=500)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@requires_csrf_token
|
|
52
|
+
def csrf_failure(request, reason="", template_name="errors/403.html"):
|
|
53
|
+
if _expects_json(request):
|
|
54
|
+
return JsonResponse({"detail": "CSRF verification failed."}, status=403)
|
|
55
|
+
return render(request, template_name, status=403)
|
|
56
|
+
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# ------------------------------------------------------------------------------
|
|
2
|
+
# Runtime
|
|
3
|
+
# ------------------------------------------------------------------------------
|
|
4
|
+
CODEFORTIFY_ENVIRONMENT=dev
|
|
5
|
+
DEBUG=True
|
|
6
|
+
SECRET_KEY=change-me
|
|
7
|
+
ALLOWED_HOSTS=localhost,127.0.0.1,0.0.0.0
|
|
8
|
+
CSRF_TRUSTED_ORIGINS=http://localhost:8000,http://127.0.0.1:8000
|
|
9
|
+
TIME_ZONE=UTC
|
|
10
|
+
LANGUAGE_CODE=en-us
|
|
11
|
+
|
|
12
|
+
# ------------------------------------------------------------------------------
|
|
13
|
+
# Database
|
|
14
|
+
# ------------------------------------------------------------------------------
|
|
15
|
+
[% if use_sqlite %]
|
|
16
|
+
DATABASE_URL=
|
|
17
|
+
[% elif use_postgres %]
|
|
18
|
+
[% if use_docker %]
|
|
19
|
+
# Leave blank for local SQLite fallback.
|
|
20
|
+
# DATABASE_URL=sqlite:///db.sqlite3
|
|
21
|
+
# Docker PostgreSQL example:
|
|
22
|
+
# DATABASE_URL=postgres://[[ project_slug ]]:[[ project_slug ]]@db:5432/[[ project_slug ]]
|
|
23
|
+
[% endif %]
|
|
24
|
+
DATABASE_URL=
|
|
25
|
+
POSTGRES_DB=[[ project_slug ]]
|
|
26
|
+
POSTGRES_USER=[[ project_slug ]]
|
|
27
|
+
POSTGRES_PASSWORD=change-me
|
|
28
|
+
POSTGRES_HOST=127.0.0.1
|
|
29
|
+
POSTGRES_PORT=5432
|
|
30
|
+
[% elif use_mysql %]
|
|
31
|
+
[% if use_docker %]
|
|
32
|
+
# Leave blank for local SQLite fallback.
|
|
33
|
+
# DATABASE_URL=sqlite:///db.sqlite3
|
|
34
|
+
# Docker MySQL example:
|
|
35
|
+
# DATABASE_URL=mysql://[[ project_slug ]]:[[ project_slug ]]@db:3306/[[ project_slug ]]
|
|
36
|
+
[% endif %]
|
|
37
|
+
DATABASE_URL=
|
|
38
|
+
MYSQL_DATABASE=[[ project_slug ]]
|
|
39
|
+
MYSQL_USER=[[ project_slug ]]
|
|
40
|
+
MYSQL_PASSWORD=change-me
|
|
41
|
+
MYSQL_HOST=127.0.0.1
|
|
42
|
+
MYSQL_PORT=3306
|
|
43
|
+
[% endif %]
|
|
44
|
+
DB_CONN_MAX_AGE=60
|
|
45
|
+
|
|
46
|
+
[% if use_celery %]
|
|
47
|
+
# ------------------------------------------------------------------------------
|
|
48
|
+
# Redis / Celery
|
|
49
|
+
# ------------------------------------------------------------------------------
|
|
50
|
+
[% if use_docker %]
|
|
51
|
+
REDIS_URL=redis://redis:6379/0
|
|
52
|
+
CELERY_BROKER_URL=redis://redis:6379/0
|
|
53
|
+
CELERY_RESULT_BACKEND=redis://redis:6379/1
|
|
54
|
+
[% else %]
|
|
55
|
+
REDIS_URL=redis://127.0.0.1:6379/0
|
|
56
|
+
CELERY_BROKER_URL=redis://127.0.0.1:6379/0
|
|
57
|
+
CELERY_RESULT_BACKEND=redis://127.0.0.1:6379/1
|
|
58
|
+
[% endif %]
|
|
59
|
+
[% endif %]
|
|
60
|
+
|
|
61
|
+
# ------------------------------------------------------------------------------
|
|
62
|
+
# Deployment / security flags
|
|
63
|
+
# ------------------------------------------------------------------------------
|
|
64
|
+
SECURE_SSL_REDIRECT=False
|
|
65
|
+
RUN_MIGRATIONS=true
|
|
66
|
+
COLLECT_STATIC=false
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
.Python
|
|
6
|
+
|
|
7
|
+
# Virtual environments
|
|
8
|
+
.venv/
|
|
9
|
+
venv/
|
|
10
|
+
env/
|
|
11
|
+
|
|
12
|
+
# Django
|
|
13
|
+
db.sqlite3
|
|
14
|
+
*.sqlite3
|
|
15
|
+
media/
|
|
16
|
+
static_root/
|
|
17
|
+
|
|
18
|
+
# Runtime and tooling
|
|
19
|
+
.env
|
|
20
|
+
.env.local
|
|
21
|
+
.pytest_cache/
|
|
22
|
+
.mypy_cache/
|
|
23
|
+
.coverage
|
|
24
|
+
htmlcov/
|
|
25
|
+
|
|
26
|
+
# Build artifacts
|
|
27
|
+
build/
|
|
28
|
+
dist/
|
|
29
|
+
|
|
30
|
+
# IDE / OS files
|
|
31
|
+
.idea/
|
|
32
|
+
.vscode/
|
|
33
|
+
.DS_Store
|
|
34
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
from core.env import configure_environment
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def main() -> None:
|
|
8
|
+
configure_environment()
|
|
9
|
+
from django.core.management import execute_from_command_line
|
|
10
|
+
|
|
11
|
+
execute_from_command_line(sys.argv)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
if __name__ == "__main__":
|
|
15
|
+
main()
|
|
16
|
+
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
color-scheme: light dark;
|
|
3
|
+
font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
body {
|
|
7
|
+
margin: 0;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.container {
|
|
11
|
+
max-width: 960px;
|
|
12
|
+
margin: 0 auto;
|
|
13
|
+
padding: 2rem 1rem;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
h1 {
|
|
17
|
+
margin-top: 0;
|
|
18
|
+
}
|
|
19
|
+
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{% load static %}
|
|
2
|
+
<!doctype html>
|
|
3
|
+
<html lang="en">
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="utf-8">
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
7
|
+
<title>{% block title %}[[ project_title ]]{% endblock %}</title>
|
|
8
|
+
<link rel="stylesheet" href="{% static 'css/main.css' %}">
|
|
9
|
+
[% if use_htmx %]
|
|
10
|
+
<script src="https://unpkg.com/htmx.org@2.0.4"></script>
|
|
11
|
+
[% endif %]
|
|
12
|
+
</head>
|
|
13
|
+
<body>
|
|
14
|
+
<main class="container">
|
|
15
|
+
{% block content %}{% endblock %}
|
|
16
|
+
</main>
|
|
17
|
+
</body>
|
|
18
|
+
</html>
|
|
19
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from celery import Celery
|
|
2
|
+
|
|
3
|
+
from core.env import configure_environment
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
configure_environment()
|
|
7
|
+
|
|
8
|
+
app = Celery("[[ project_slug ]]")
|
|
9
|
+
app.config_from_object("django.conf:settings", namespace="CELERY")
|
|
10
|
+
app.autodiscover_tasks()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@app.task(bind=True)
|
|
14
|
+
def debug_task(self): # pragma: no cover - smoke task
|
|
15
|
+
return f"Celery debug task executed: {self.request!r}"
|
|
16
|
+
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Docker Guide
|
|
2
|
+
|
|
3
|
+
This project includes Docker support for local and production-style workflows.
|
|
4
|
+
|
|
5
|
+
## Services
|
|
6
|
+
|
|
7
|
+
- `web` (Django)
|
|
8
|
+
[% if use_postgres %]- `db` (PostgreSQL)
|
|
9
|
+
[% elif use_mysql %]- `db` (MySQL)
|
|
10
|
+
[% else %]- `db` is not used (SQLite)
|
|
11
|
+
[% endif %]
|
|
12
|
+
[% if use_celery %]- `redis` (broker/result backend)
|
|
13
|
+
- `celery` (worker)
|
|
14
|
+
- `celery-beat` (optional profile)
|
|
15
|
+
[% endif %]
|
|
16
|
+
|
|
17
|
+
## Start local stack
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
cp .env.example .env
|
|
21
|
+
docker compose build
|
|
22
|
+
docker compose up -d
|
|
23
|
+
docker compose exec web python manage.py check
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Stop stack
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
docker compose down
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Production-style compose
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
docker compose -f docker-compose.prod.yml up -d --build
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
[% if use_celery %]
|
|
39
|
+
## Celery
|
|
40
|
+
|
|
41
|
+
Inspect worker logs:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
docker compose logs -f celery
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Start beat profile:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
docker compose --profile beat up -d celery-beat
|
|
51
|
+
```
|
|
52
|
+
[% endif %]
|
|
53
|
+
|
|
54
|
+
## Helper script
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
./docker_deploy.sh up
|
|
58
|
+
./docker_deploy.sh check
|
|
59
|
+
./docker_deploy.sh logs web
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
[% if use_sqlite %]
|
|
63
|
+
## Note about SQLite with Docker
|
|
64
|
+
|
|
65
|
+
SQLite is supported for development convenience but is not suitable for multi-container production workloads.
|
|
66
|
+
[% endif %]
|
|
67
|
+
|