djangx 1.5.6__tar.gz → 1.5.7__tar.gz
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.
- {djangx-1.5.6 → djangx-1.5.7}/PKG-INFO +1 -1
- {djangx-1.5.6 → djangx-1.5.7}/pyproject.toml +1 -1
- djangx-1.5.7/src/djangx/api/enums.py +8 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/api/settings/databases.py +9 -8
- djangx-1.5.7/src/djangx/enums.py +2 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/management/cli.py +14 -1
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/management/commands/generate.py +1 -11
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/management/commands/startproject.py +209 -71
- djangx-1.5.7/src/djangx/management/enums.py +63 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/management/settings/apps.py +39 -82
- {djangx-1.5.6 → djangx-1.5.7}/LICENSE +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/README.md +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/__init__.py +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/api/__init__.py +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/api/backends/__init__.py +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/api/backends/auth.py +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/api/backends/server/__init__.py +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/api/backends/server/asgi.py +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/api/backends/server/wsgi.py +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/api/backends/storages.py +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/api/settings/__init__.py +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/api/settings/auth.py +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/api/settings/server.py +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/api/settings/storages.py +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/api/types/__init__.py +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/api/types/auth.py +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/api/types/databases.py +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/api/types/storages.py +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/api/urls.py +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/management/__init__.py +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/management/commands/__init__.py +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/management/commands/collectstatic.py +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/management/commands/helpers/__init__.py +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/management/commands/helpers/art.py +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/management/commands/helpers/run.py +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/management/commands/runbuild.py +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/management/commands/runinstall.py +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/management/commands/runserver.py +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/management/commands/tailwind.py +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/management/settings/__init__.py +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/management/settings/runcommands.py +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/management/settings/security.py +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/management/settings/tailwind.py +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/management/types/__init__.py +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/management/types/apps.py +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/management/urls.py +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/py.typed +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/settings.py +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/types.py +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/ui/__init__.py +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/ui/admin.py +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/ui/settings/__init__.py +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/ui/settings/contactinfo.py +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/ui/settings/org.py +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/ui/settings/social.py +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/ui/static/ui/css/.gitignore +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/ui/static/ui/css/aos.css +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/ui/static/ui/css/bootstrap-icons.min.css +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/ui/static/ui/css/fonts/bootstrap-icons.woff +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/ui/static/ui/css/fonts/bootstrap-icons.woff2 +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/ui/static/ui/img/apple-touch-icon.png +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/ui/static/ui/img/favicon.ico +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/ui/static/ui/img/logo.png +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/ui/static/ui/js/aos-init.js +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/ui/static/ui/js/aos.js +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/ui/static/ui/js/preloader.js +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/ui/static/ui/js/scroll-top.js +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/ui/templates/ui/footer.html +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/ui/templates/ui/header.html +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/ui/templates/ui/hero.html +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/ui/templates/ui/index.html +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/ui/templates/ui/preloader.html +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/ui/templates/ui/scroll-top.html +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/ui/templatetags/__init__.py +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/ui/templatetags/contactinfo.py +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/ui/templatetags/org.py +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/ui/templatetags/social.py +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/ui/templatetags/tailwind_css.py +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/ui/types/__init__.py +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/ui/types/contactinfo.py +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/ui/types/org.py +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/ui/types/social.py +0 -0
- {djangx-1.5.6 → djangx-1.5.7}/src/djangx/ui/urls.py +0 -0
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
# https://www.postgresql.org/docs/current/libpq-pgpass.html
|
|
6
6
|
# ==============================================================================
|
|
7
7
|
from ... import PROJECT_DIR, Conf, ConfField
|
|
8
|
+
from ..enums import DatabaseBackend
|
|
8
9
|
from ..types import DatabaseDict, DatabaseOptionsDict, DatabasesDict
|
|
9
10
|
|
|
10
11
|
|
|
@@ -13,10 +14,10 @@ class DatabaseConf(Conf):
|
|
|
13
14
|
|
|
14
15
|
backend = ConfField(
|
|
15
16
|
type=str,
|
|
16
|
-
choices=[
|
|
17
|
+
choices=[DatabaseBackend.SQLITE3, DatabaseBackend.POSTGRESQL],
|
|
17
18
|
env="DB_BACKEND",
|
|
18
19
|
toml="db.backend",
|
|
19
|
-
default=
|
|
20
|
+
default=DatabaseBackend.SQLITE3,
|
|
20
21
|
)
|
|
21
22
|
# postgresql specific
|
|
22
23
|
use_vars = ConfField(
|
|
@@ -79,14 +80,14 @@ def _get_databases_config() -> DatabasesDict:
|
|
|
79
80
|
backend: str = _DATABASE.backend.lower()
|
|
80
81
|
|
|
81
82
|
match backend:
|
|
82
|
-
case
|
|
83
|
+
case DatabaseBackend.SQLITE3:
|
|
83
84
|
return {
|
|
84
85
|
"default": {
|
|
85
|
-
"ENGINE": "django.db.backends.
|
|
86
|
-
"NAME": PROJECT_DIR / "db.
|
|
86
|
+
"ENGINE": f"django.db.backends.{DatabaseBackend.SQLITE3.value}",
|
|
87
|
+
"NAME": PROJECT_DIR / f"db.{DatabaseBackend.SQLITE3.value}",
|
|
87
88
|
}
|
|
88
89
|
}
|
|
89
|
-
case
|
|
90
|
+
case DatabaseBackend.POSTGRESQL:
|
|
90
91
|
options: DatabaseOptionsDict = {
|
|
91
92
|
"pool": _DATABASE.pool,
|
|
92
93
|
"sslmode": _DATABASE.ssl_mode,
|
|
@@ -95,7 +96,7 @@ def _get_databases_config() -> DatabasesDict:
|
|
|
95
96
|
# Add service or connection vars
|
|
96
97
|
if _DATABASE.use_vars:
|
|
97
98
|
config: DatabaseDict = {
|
|
98
|
-
"ENGINE": "django.db.backends.
|
|
99
|
+
"ENGINE": f"django.db.backends.{DatabaseBackend.POSTGRESQL.value}",
|
|
99
100
|
"NAME": _DATABASE.name,
|
|
100
101
|
"USER": _DATABASE.user,
|
|
101
102
|
"PASSWORD": _DATABASE.password,
|
|
@@ -106,7 +107,7 @@ def _get_databases_config() -> DatabasesDict:
|
|
|
106
107
|
else:
|
|
107
108
|
options["service"] = _DATABASE.service
|
|
108
109
|
config: DatabaseDict = {
|
|
109
|
-
"ENGINE": "django.db.backends.
|
|
110
|
+
"ENGINE": f"django.db.backends.{DatabaseBackend.POSTGRESQL.value}",
|
|
110
111
|
"NAME": _DATABASE.name,
|
|
111
112
|
"OPTIONS": options,
|
|
112
113
|
}
|
|
@@ -17,6 +17,7 @@ def main() -> Optional[NoReturn]:
|
|
|
17
17
|
case "startproject" | "init" | "new":
|
|
18
18
|
from argparse import ArgumentParser, Namespace
|
|
19
19
|
|
|
20
|
+
from ..enums import DatabaseBackend
|
|
20
21
|
from .commands.startproject import initialize
|
|
21
22
|
|
|
22
23
|
parser = ArgumentParser(description=f"Initialize a new {PKG_DISPLAY_NAME} project")
|
|
@@ -25,9 +26,21 @@ def main() -> Optional[NoReturn]:
|
|
|
25
26
|
choices=["default", "vercel"],
|
|
26
27
|
help="Project preset to use (skips interactive prompt)",
|
|
27
28
|
)
|
|
29
|
+
parser.add_argument(
|
|
30
|
+
"--database",
|
|
31
|
+
"--db",
|
|
32
|
+
choices=[DatabaseBackend.SQLITE3, DatabaseBackend.POSTGRESQL],
|
|
33
|
+
help=f"Database backend to use (skips interactive prompt). Note: Vercel preset requires {DatabaseBackend.POSTGRESQL.value}.",
|
|
34
|
+
)
|
|
35
|
+
parser.add_argument(
|
|
36
|
+
"--force",
|
|
37
|
+
"-f",
|
|
38
|
+
action="store_true",
|
|
39
|
+
help="Skip directory validation and initialize even if directory is not empty",
|
|
40
|
+
)
|
|
28
41
|
args: Namespace = parser.parse_args(sys.argv[2:])
|
|
29
42
|
|
|
30
|
-
sys.exit(initialize(preset=args.preset))
|
|
43
|
+
sys.exit(initialize(preset=args.preset, database=args.database, force=args.force))
|
|
31
44
|
|
|
32
45
|
case _:
|
|
33
46
|
from os import environ
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import builtins
|
|
2
2
|
import pathlib
|
|
3
|
-
from enum import StrEnum
|
|
4
3
|
from typing import Any, Optional, cast
|
|
5
4
|
|
|
6
5
|
from christianwhocodes.generators import (
|
|
7
6
|
FileGenerator,
|
|
8
|
-
FileGeneratorOption,
|
|
9
7
|
PgPassFileGenerator,
|
|
10
8
|
PgServiceFileGenerator,
|
|
11
9
|
SSHConfigFileGenerator,
|
|
@@ -13,6 +11,7 @@ from christianwhocodes.generators import (
|
|
|
13
11
|
from django.core.management.base import BaseCommand, CommandParser
|
|
14
12
|
|
|
15
13
|
from ... import PKG_DISPLAY_NAME, PKG_NAME, PROJECT_API_DIR, PROJECT_DIR, Conf
|
|
14
|
+
from ..enums import FileOption
|
|
16
15
|
|
|
17
16
|
|
|
18
17
|
class _ServerFileGenerator(FileGenerator):
|
|
@@ -222,15 +221,6 @@ class _EnvFileGenerator(FileGenerator):
|
|
|
222
221
|
return str(value)
|
|
223
222
|
|
|
224
223
|
|
|
225
|
-
class FileOption(StrEnum):
|
|
226
|
-
PG_SERVICE = FileGeneratorOption.PG_SERVICE.value
|
|
227
|
-
PGPASS = FileGeneratorOption.PGPASS.value
|
|
228
|
-
SSH_CONFIG = FileGeneratorOption.SSH_CONFIG.value
|
|
229
|
-
ENV = "env"
|
|
230
|
-
SERVER = "server"
|
|
231
|
-
VERCEL = "vercel"
|
|
232
|
-
|
|
233
|
-
|
|
234
224
|
class Command(BaseCommand):
|
|
235
225
|
help: str = "Generate configuration files (e.g., .env.example, vercel.json, asgi.py, wsgi.py, .pg_service.conf, pgpass.conf / .pgpass, ssh config)."
|
|
236
226
|
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
|
-
from enum import StrEnum
|
|
3
2
|
from pathlib import Path
|
|
4
3
|
from typing import Final
|
|
5
4
|
|
|
@@ -9,6 +8,7 @@ from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
|
9
8
|
from rich.prompt import Confirm, Prompt
|
|
10
9
|
|
|
11
10
|
from ... import PKG_DISPLAY_NAME, PKG_NAME, PROJECT_DIR, PROJECT_INIT_NAME
|
|
11
|
+
from ...enums import DatabaseBackend, PresetType
|
|
12
12
|
|
|
13
13
|
__all__ = ["initialize"]
|
|
14
14
|
|
|
@@ -29,29 +29,21 @@ SAFE_DIRECTORY_ITEMS: Final[set[str]] = {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
|
|
32
|
-
class PresetType(StrEnum):
|
|
33
|
-
"""Available project presets."""
|
|
34
|
-
|
|
35
|
-
DEFAULT = "default"
|
|
36
|
-
VERCEL = "vercel"
|
|
37
|
-
|
|
38
|
-
|
|
39
32
|
@dataclass(frozen=True)
|
|
40
33
|
class _ProjectDependencies:
|
|
41
34
|
"""Manages project dependencies."""
|
|
42
35
|
|
|
43
|
-
base: tuple[str, ...] = (
|
|
44
|
-
"pillow>=12.1.0",
|
|
45
|
-
"psycopg[binary,pool]>=3.3.2",
|
|
46
|
-
)
|
|
36
|
+
base: tuple[str, ...] = ("pillow>=12.1.0",)
|
|
47
37
|
dev: tuple[str, ...] = ("djlint>=1.36.4", "ruff>=0.15.0")
|
|
38
|
+
postgresql: tuple[str, ...] = ("psycopg[binary,pool]>=3.3.2",)
|
|
48
39
|
vercel: tuple[str, ...] = ("vercel>=0.3.8",)
|
|
49
40
|
|
|
50
|
-
def
|
|
51
|
-
"""Get dependencies for a specific
|
|
41
|
+
def get_for_config(self, preset: PresetType, database: DatabaseBackend) -> list[str]:
|
|
42
|
+
"""Get dependencies for a specific configuration.
|
|
52
43
|
|
|
53
44
|
Args:
|
|
54
45
|
preset: The preset type.
|
|
46
|
+
database: The database backend.
|
|
55
47
|
|
|
56
48
|
Returns:
|
|
57
49
|
List of dependency strings including base dependencies.
|
|
@@ -59,6 +51,11 @@ class _ProjectDependencies:
|
|
|
59
51
|
deps = list(self.base)
|
|
60
52
|
deps.append(f"{PKG_NAME}>={Version.get(PKG_NAME)[0]}")
|
|
61
53
|
|
|
54
|
+
# Add database-specific dependencies
|
|
55
|
+
if database == DatabaseBackend.POSTGRESQL:
|
|
56
|
+
deps.extend(self.postgresql)
|
|
57
|
+
|
|
58
|
+
# Add preset-specific dependencies
|
|
62
59
|
if preset == PresetType.VERCEL:
|
|
63
60
|
deps.extend(self.vercel)
|
|
64
61
|
|
|
@@ -71,7 +68,7 @@ class _TemplateManager:
|
|
|
71
68
|
@staticmethod
|
|
72
69
|
def gitignore() -> str:
|
|
73
70
|
"""Generate .gitignore content."""
|
|
74
|
-
return """
|
|
71
|
+
return f"""
|
|
75
72
|
# Python-generated files
|
|
76
73
|
__pycache__/
|
|
77
74
|
*.py[oc]
|
|
@@ -95,8 +92,8 @@ wheels/
|
|
|
95
92
|
# Environment variables files
|
|
96
93
|
/.env*
|
|
97
94
|
|
|
98
|
-
#
|
|
99
|
-
/db.
|
|
95
|
+
# {DatabaseBackend.SQLITE3.value.capitalize()} database file
|
|
96
|
+
/db.{DatabaseBackend.SQLITE3.value}
|
|
100
97
|
""".strip()
|
|
101
98
|
|
|
102
99
|
@staticmethod
|
|
@@ -110,16 +107,23 @@ A new project built with {PKG_DISPLAY_NAME}.
|
|
|
110
107
|
## Getting Started
|
|
111
108
|
|
|
112
109
|
1. Install dependencies: `uv sync`
|
|
113
|
-
2. Run development server: `djx runserver`
|
|
110
|
+
2. Run development server: `uv run djx runserver`
|
|
114
111
|
""".strip()
|
|
115
112
|
|
|
116
113
|
@staticmethod
|
|
117
|
-
def pyproject_toml(
|
|
114
|
+
def pyproject_toml(
|
|
115
|
+
preset: PresetType,
|
|
116
|
+
database: DatabaseBackend,
|
|
117
|
+
dependencies: list[str],
|
|
118
|
+
use_postgres_env_vars: bool = False,
|
|
119
|
+
) -> str:
|
|
118
120
|
"""Generate pyproject.toml content.
|
|
119
121
|
|
|
120
122
|
Args:
|
|
121
123
|
preset: The preset type.
|
|
124
|
+
database: The database backend.
|
|
122
125
|
dependencies: List of project dependencies.
|
|
126
|
+
use_postgres_env_vars: Whether to use env vars for PostgreSQL config.
|
|
123
127
|
|
|
124
128
|
Returns:
|
|
125
129
|
Formatted pyproject.toml content.
|
|
@@ -128,15 +132,25 @@ A new project built with {PKG_DISPLAY_NAME}.
|
|
|
128
132
|
deps_formatted = ",\n ".join(f'"{dep}"' for dep in dependencies)
|
|
129
133
|
dev_deps_formatted = ",\n ".join(f'"{dep}"' for dep in deps.dev)
|
|
130
134
|
|
|
131
|
-
#
|
|
135
|
+
# Build tool configuration based on preset and database
|
|
136
|
+
tool_config_parts: list[str] = []
|
|
137
|
+
|
|
138
|
+
# Database configuration
|
|
139
|
+
if database == DatabaseBackend.POSTGRESQL:
|
|
140
|
+
tool_config_parts.append(
|
|
141
|
+
f'db = {{ backend = "{DatabaseBackend.POSTGRESQL.value}", use-vars = {str(use_postgres_env_vars).lower()} }}'
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
# Storage configuration (Vercel-specific)
|
|
145
|
+
if preset == PresetType.VERCEL:
|
|
146
|
+
tool_config_parts.append(
|
|
147
|
+
'storage = { backend = "vercel", blob-token = "keep-your-vercel-blob-token-secret-in-env" }'
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
# Format the tool config section
|
|
132
151
|
tool_config = ""
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
tool_config = (
|
|
136
|
-
'\nstorage = { backend = "vercel", blob-token = "your-vercel-blob-token" }\n'
|
|
137
|
-
)
|
|
138
|
-
case _:
|
|
139
|
-
pass
|
|
152
|
+
if tool_config_parts:
|
|
153
|
+
tool_config = "\n" + "\n".join(tool_config_parts) + "\n"
|
|
140
154
|
|
|
141
155
|
return f"""[project]
|
|
142
156
|
name = "{PROJECT_INIT_NAME}"
|
|
@@ -213,18 +227,18 @@ class HomeView(TemplateView):
|
|
|
213
227
|
@staticmethod
|
|
214
228
|
def tailwind_css() -> str:
|
|
215
229
|
"""Generate Tailwind CSS content."""
|
|
216
|
-
return """@import "tailwindcss";
|
|
230
|
+
return f"""@import "tailwindcss";
|
|
217
231
|
|
|
218
232
|
/* =============================================================================
|
|
219
233
|
SOURCE FILES
|
|
220
234
|
============================================================================= */
|
|
221
|
-
@source "../../../../.venv/**/
|
|
235
|
+
@source "../../../../.venv/**/{PKG_NAME}/ui/templates/ui/**/*.html";
|
|
222
236
|
@source "../../../templates/home/**/*.html";
|
|
223
237
|
|
|
224
238
|
/* =============================================================================
|
|
225
239
|
THEME CONFIGURATION
|
|
226
240
|
============================================================================= */
|
|
227
|
-
@theme {
|
|
241
|
+
@theme {{
|
|
228
242
|
/* ---------------------------------------------------------------------------
|
|
229
243
|
TYPOGRAPHY
|
|
230
244
|
--------------------------------------------------------------------------- */
|
|
@@ -263,70 +277,70 @@ class HomeView(TemplateView):
|
|
|
263
277
|
--color-nav-dropdown-bg: #2e2e2e; /* Dropdown background */
|
|
264
278
|
--color-nav-dropdown: #d9d9d9; /* Dropdown text color */
|
|
265
279
|
--color-nav-dropdown-hover: #ff4d4f; /* Dropdown hover state */
|
|
266
|
-
}
|
|
280
|
+
}}
|
|
267
281
|
|
|
268
282
|
/* =============================================================================
|
|
269
283
|
LIGHT THEME OVERRIDES
|
|
270
284
|
============================================================================= */
|
|
271
|
-
@theme light {
|
|
285
|
+
@theme light {{
|
|
272
286
|
--color-background: rgba(41, 41, 41, 0.8);
|
|
273
287
|
--color-surface: #484848;
|
|
274
|
-
}
|
|
288
|
+
}}
|
|
275
289
|
|
|
276
290
|
/* =============================================================================
|
|
277
291
|
DARK THEME OVERRIDES
|
|
278
292
|
============================================================================= */
|
|
279
|
-
@theme dark {
|
|
293
|
+
@theme dark {{
|
|
280
294
|
--color-background: #060606;
|
|
281
295
|
--color-surface: #252525;
|
|
282
296
|
--color-default: #ffffff;
|
|
283
297
|
--color-heading: #ffffff;
|
|
284
|
-
}
|
|
298
|
+
}}
|
|
285
299
|
|
|
286
300
|
/* =============================================================================
|
|
287
301
|
UTILITY CLASSES
|
|
288
302
|
============================================================================= */
|
|
289
|
-
@layer utilities {
|
|
303
|
+
@layer utilities {{
|
|
290
304
|
/* Full-width container */
|
|
291
|
-
.container-full {
|
|
305
|
+
.container-full {{
|
|
292
306
|
@apply mx-auto w-full px-8;
|
|
293
|
-
}
|
|
307
|
+
}}
|
|
294
308
|
|
|
295
309
|
/* Responsive container (Mobile→SM→MD→LG→XL→2XL: 100%→92%→83%→80%→75%→1400px max) */
|
|
296
|
-
.container {
|
|
310
|
+
.container {{
|
|
297
311
|
@apply mx-auto w-full px-8 sm:w-11/12 sm:px-4 md:w-5/6 lg:w-4/5 xl:w-3/4 xl:px-0 2xl:max-w-[1400px];
|
|
298
|
-
}
|
|
299
|
-
}
|
|
312
|
+
}}
|
|
313
|
+
}}
|
|
300
314
|
|
|
301
315
|
/* =============================================================================
|
|
302
316
|
BASE STYLES - Global element styling
|
|
303
317
|
============================================================================= */
|
|
304
|
-
@layer base {
|
|
305
|
-
:root {
|
|
318
|
+
@layer base {{
|
|
319
|
+
:root {{
|
|
306
320
|
@apply scroll-smooth;
|
|
307
|
-
}
|
|
321
|
+
}}
|
|
308
322
|
|
|
309
|
-
body {
|
|
323
|
+
body {{
|
|
310
324
|
@apply bg-background text-default font-default antialiased;
|
|
311
|
-
}
|
|
325
|
+
}}
|
|
312
326
|
|
|
313
327
|
h1,
|
|
314
328
|
h2,
|
|
315
329
|
h3,
|
|
316
330
|
h4,
|
|
317
331
|
h5,
|
|
318
|
-
h6 {
|
|
332
|
+
h6 {{
|
|
319
333
|
@apply text-heading font-heading text-balance;
|
|
320
|
-
}
|
|
334
|
+
}}
|
|
321
335
|
|
|
322
|
-
a {
|
|
336
|
+
a {{
|
|
323
337
|
@apply text-accent no-underline transition-colors duration-200 ease-in-out;
|
|
324
|
-
}
|
|
338
|
+
}}
|
|
325
339
|
|
|
326
|
-
a:hover {
|
|
340
|
+
a:hover {{
|
|
327
341
|
color: color-mix(in srgb, var(--color-accent), white 15%);
|
|
328
|
-
}
|
|
329
|
-
}
|
|
342
|
+
}}
|
|
343
|
+
}}
|
|
330
344
|
"""
|
|
331
345
|
|
|
332
346
|
|
|
@@ -646,20 +660,110 @@ class _ProjectInitializer:
|
|
|
646
660
|
)
|
|
647
661
|
return PresetType(choice)
|
|
648
662
|
|
|
649
|
-
def
|
|
663
|
+
def _get_postgres_config_choice(self, preset: PresetType) -> bool:
|
|
664
|
+
"""Get whether to use environment variables for PostgreSQL.
|
|
665
|
+
|
|
666
|
+
Args:
|
|
667
|
+
preset: The preset type (Vercel always uses env vars).
|
|
668
|
+
|
|
669
|
+
Returns:
|
|
670
|
+
True if using environment variables, False for pg_service/pgpass files.
|
|
671
|
+
"""
|
|
672
|
+
# Vercel requires environment variables (no filesystem access)
|
|
673
|
+
if preset == PresetType.VERCEL:
|
|
674
|
+
return True
|
|
675
|
+
|
|
676
|
+
# For default preset, let user choose
|
|
677
|
+
self.console.print("\n[bold]PostgreSQL Configuration Method:[/bold]")
|
|
678
|
+
self.console.print(" • [cyan]Environment variables[/cyan]: Store credentials in .env file")
|
|
679
|
+
self.console.print(
|
|
680
|
+
" • [cyan]PostgreSQL service files[/cyan]: Use pg_service.conf and .pgpass"
|
|
681
|
+
)
|
|
682
|
+
|
|
683
|
+
use_env_vars = Confirm.ask(
|
|
684
|
+
"Use environment variables for PostgreSQL configuration?",
|
|
685
|
+
default=True,
|
|
686
|
+
console=self.console,
|
|
687
|
+
)
|
|
688
|
+
|
|
689
|
+
return use_env_vars
|
|
690
|
+
|
|
691
|
+
def _get_database_choice(
|
|
692
|
+
self, preset: PresetType, database: str | None = None
|
|
693
|
+
) -> tuple[DatabaseBackend, bool]:
|
|
694
|
+
"""Get the user's database choice and PostgreSQL config method.
|
|
695
|
+
|
|
696
|
+
Args:
|
|
697
|
+
preset: The preset type (Vercel requires PostgreSQL).
|
|
698
|
+
database: Optional database to use without prompting.
|
|
699
|
+
|
|
700
|
+
Returns:
|
|
701
|
+
Tuple of (database backend, use_env_vars_for_postgres).
|
|
702
|
+
|
|
703
|
+
Raises:
|
|
704
|
+
ValueError: If database is invalid or incompatible with preset.
|
|
705
|
+
"""
|
|
706
|
+
# Vercel preset requires PostgreSQL (no file system access on Vercel)
|
|
707
|
+
if preset == PresetType.VERCEL:
|
|
708
|
+
if database and database != DatabaseBackend.POSTGRESQL:
|
|
709
|
+
raise ValueError(f"Vercel preset requires PostgreSQL database (got: {database})")
|
|
710
|
+
return DatabaseBackend.POSTGRESQL, True # Always use env vars with Vercel
|
|
711
|
+
|
|
712
|
+
# If database explicitly provided, validate it
|
|
713
|
+
if database:
|
|
714
|
+
try:
|
|
715
|
+
db_backend = DatabaseBackend(database)
|
|
716
|
+
except ValueError:
|
|
717
|
+
valid_databases = [db.value for db in DatabaseBackend]
|
|
718
|
+
raise ValueError(
|
|
719
|
+
f"Invalid database '{database}'. Must be one of: {', '.join(valid_databases)}"
|
|
720
|
+
) from None
|
|
721
|
+
|
|
722
|
+
# Ask about PostgreSQL config method if applicable
|
|
723
|
+
use_env_vars = (
|
|
724
|
+
self._get_postgres_config_choice(preset)
|
|
725
|
+
if db_backend == DatabaseBackend.POSTGRESQL
|
|
726
|
+
else False
|
|
727
|
+
)
|
|
728
|
+
return db_backend, use_env_vars
|
|
729
|
+
|
|
730
|
+
# Interactive prompt for default preset
|
|
731
|
+
choice = Prompt.ask(
|
|
732
|
+
"Choose a database",
|
|
733
|
+
choices=[db.value for db in DatabaseBackend],
|
|
734
|
+
default=DatabaseBackend.SQLITE3.value,
|
|
735
|
+
console=self.console,
|
|
736
|
+
)
|
|
737
|
+
db_backend = DatabaseBackend(choice)
|
|
738
|
+
|
|
739
|
+
# Ask about PostgreSQL config method if applicable
|
|
740
|
+
use_env_vars = (
|
|
741
|
+
self._get_postgres_config_choice(preset)
|
|
742
|
+
if db_backend == DatabaseBackend.POSTGRESQL
|
|
743
|
+
else False
|
|
744
|
+
)
|
|
745
|
+
return db_backend, use_env_vars
|
|
746
|
+
|
|
747
|
+
def _create_core_files(
|
|
748
|
+
self, preset: PresetType, database: DatabaseBackend, use_postgres_env_vars: bool
|
|
749
|
+
) -> None:
|
|
650
750
|
"""Create core project configuration files.
|
|
651
751
|
|
|
652
752
|
Args:
|
|
653
753
|
preset: The preset type to use.
|
|
754
|
+
database: The database backend to use.
|
|
755
|
+
use_postgres_env_vars: Whether to use env vars for PostgreSQL config.
|
|
654
756
|
|
|
655
757
|
Raises:
|
|
656
758
|
IOError: If files cannot be created.
|
|
657
759
|
"""
|
|
658
|
-
dependencies = self.dependencies.
|
|
760
|
+
dependencies = self.dependencies.get_for_config(preset, database)
|
|
659
761
|
|
|
660
762
|
# Create files only if they don't exist
|
|
661
763
|
files_to_create = {
|
|
662
|
-
"pyproject.toml": self.templates.pyproject_toml(
|
|
764
|
+
"pyproject.toml": self.templates.pyproject_toml(
|
|
765
|
+
preset, database, dependencies, use_postgres_env_vars
|
|
766
|
+
),
|
|
663
767
|
".gitignore": self.templates.gitignore(),
|
|
664
768
|
"README.md": self.templates.readme(),
|
|
665
769
|
}
|
|
@@ -749,11 +853,15 @@ class _ProjectInitializer:
|
|
|
749
853
|
home_creator = _HomeAppCreator(self.project_dir, self.writer, self.templates, self.console)
|
|
750
854
|
home_creator.create()
|
|
751
855
|
|
|
752
|
-
def _show_next_steps(
|
|
856
|
+
def _show_next_steps(
|
|
857
|
+
self, preset: PresetType, database: DatabaseBackend, use_postgres_env_vars: bool
|
|
858
|
+
) -> None:
|
|
753
859
|
"""Display next steps for the user after successful initialization.
|
|
754
860
|
|
|
755
861
|
Args:
|
|
756
862
|
preset: The preset that was used.
|
|
863
|
+
database: The database backend that was chosen.
|
|
864
|
+
use_postgres_env_vars: Whether using env vars for PostgreSQL.
|
|
757
865
|
"""
|
|
758
866
|
from rich.panel import Panel
|
|
759
867
|
|
|
@@ -762,13 +870,28 @@ class _ProjectInitializer:
|
|
|
762
870
|
"2. Copy [bold].env.example[/bold] to [bold].env[/bold] and configure",
|
|
763
871
|
]
|
|
764
872
|
|
|
873
|
+
step_num = 3
|
|
874
|
+
|
|
875
|
+
# Database-specific instructions
|
|
876
|
+
if database == DatabaseBackend.POSTGRESQL:
|
|
877
|
+
if use_postgres_env_vars:
|
|
878
|
+
next_steps.append(
|
|
879
|
+
f"{step_num}. Configure PostgreSQL connection in [bold].env[/bold]"
|
|
880
|
+
)
|
|
881
|
+
else:
|
|
882
|
+
next_steps.append(
|
|
883
|
+
f"{step_num}. Configure PostgreSQL connection using [bold]pg_service.conf[/bold] and [bold].pgpass[/bold] files"
|
|
884
|
+
)
|
|
885
|
+
step_num += 1
|
|
886
|
+
|
|
887
|
+
# Preset-specific instructions
|
|
765
888
|
if preset == PresetType.VERCEL:
|
|
766
|
-
next_steps.append(
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
889
|
+
next_steps.append(f"{step_num}. Configure Vercel blob token in [bold].env[/bold]")
|
|
890
|
+
step_num += 1
|
|
891
|
+
|
|
892
|
+
next_steps.append(
|
|
893
|
+
f"{step_num}. Run development server: [bold cyan]uv run djx runserver[/bold cyan]"
|
|
894
|
+
)
|
|
772
895
|
|
|
773
896
|
panel = Panel(
|
|
774
897
|
"\n".join(next_steps),
|
|
@@ -780,11 +903,14 @@ class _ProjectInitializer:
|
|
|
780
903
|
self.console.print("\n")
|
|
781
904
|
self.console.print(panel)
|
|
782
905
|
|
|
783
|
-
def create(
|
|
906
|
+
def create(
|
|
907
|
+
self, preset: str | None = None, database: str | None = None, force: bool = False
|
|
908
|
+
) -> ExitCode:
|
|
784
909
|
"""Execute the full project initialization workflow.
|
|
785
910
|
|
|
786
911
|
Args:
|
|
787
912
|
preset: Optional preset to use without prompting.
|
|
913
|
+
database: Optional database backend to use without prompting.
|
|
788
914
|
force: Skip directory validation.
|
|
789
915
|
|
|
790
916
|
Returns:
|
|
@@ -797,6 +923,11 @@ class _ProjectInitializer:
|
|
|
797
923
|
# Get and validate preset choice
|
|
798
924
|
chosen_preset = self._get_preset_choice(preset)
|
|
799
925
|
|
|
926
|
+
# Get and validate database choice
|
|
927
|
+
chosen_database, use_postgres_env_vars = self._get_database_choice(
|
|
928
|
+
chosen_preset, database
|
|
929
|
+
)
|
|
930
|
+
|
|
800
931
|
with Progress(
|
|
801
932
|
SpinnerColumn(),
|
|
802
933
|
TextColumn("[progress.description]{task.description}"),
|
|
@@ -805,7 +936,7 @@ class _ProjectInitializer:
|
|
|
805
936
|
) as progress:
|
|
806
937
|
# Create core configuration files
|
|
807
938
|
task = progress.add_task("Creating project files...", total=None)
|
|
808
|
-
self._create_core_files(chosen_preset)
|
|
939
|
+
self._create_core_files(chosen_preset, chosen_database, use_postgres_env_vars)
|
|
809
940
|
progress.update(task, completed=True)
|
|
810
941
|
|
|
811
942
|
# Configure preset-specific files
|
|
@@ -818,7 +949,7 @@ class _ProjectInitializer:
|
|
|
818
949
|
self._create_home_app()
|
|
819
950
|
progress.update(task, completed=True)
|
|
820
951
|
|
|
821
|
-
self._show_next_steps(chosen_preset)
|
|
952
|
+
self._show_next_steps(chosen_preset, chosen_database, use_postgres_env_vars)
|
|
822
953
|
return ExitCode.SUCCESS
|
|
823
954
|
|
|
824
955
|
except KeyboardInterrupt:
|
|
@@ -861,14 +992,19 @@ class _ProjectInitializer:
|
|
|
861
992
|
# ============================================================================
|
|
862
993
|
|
|
863
994
|
|
|
864
|
-
def initialize(
|
|
865
|
-
|
|
995
|
+
def initialize(
|
|
996
|
+
preset: str | None = None, database: str | None = None, force: bool = False
|
|
997
|
+
) -> ExitCode:
|
|
998
|
+
f"""Main entry point for project initialization.
|
|
866
999
|
|
|
867
|
-
Creates a new project with the specified preset configuration.
|
|
1000
|
+
Creates a new project with the specified preset and database configuration.
|
|
868
1001
|
|
|
869
1002
|
Args:
|
|
870
1003
|
preset: Optional preset to use without prompting.
|
|
871
1004
|
Available presets: 'default', 'vercel'.
|
|
1005
|
+
database: Optional database backend to use without prompting.
|
|
1006
|
+
Available databases: '{DatabaseBackend.SQLITE3.value}', '{DatabaseBackend.POSTGRESQL.value}'.
|
|
1007
|
+
Note: Vercel preset requires PostgreSQL.
|
|
872
1008
|
force: Skip directory validation and proceed even if directory is not empty.
|
|
873
1009
|
|
|
874
1010
|
Returns:
|
|
@@ -876,7 +1012,9 @@ def initialize(preset: str | None = None, force: bool = False) -> ExitCode:
|
|
|
876
1012
|
ExitCode.ERROR otherwise.
|
|
877
1013
|
|
|
878
1014
|
Example:
|
|
879
|
-
>>> initialize(preset="vercel")
|
|
1015
|
+
>>> initialize(preset="vercel") # Will auto-select {DatabaseBackend.POSTGRESQL.value} due to Vercel preset requirement
|
|
1016
|
+
>>> initialize(database="{DatabaseBackend.POSTGRESQL.value}") # Default preset with {DatabaseBackend.POSTGRESQL.value}
|
|
1017
|
+
>>> initialize(preset="default", database="{DatabaseBackend.SQLITE3.value}") # Default preset with {DatabaseBackend.SQLITE3.value}
|
|
880
1018
|
>>> initialize(force=True)
|
|
881
1019
|
"""
|
|
882
|
-
return _ProjectInitializer(PROJECT_DIR).create(preset=preset, force=force)
|
|
1020
|
+
return _ProjectInitializer(PROJECT_DIR).create(preset=preset, database=database, force=force)
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
from enum import StrEnum
|
|
2
|
+
|
|
3
|
+
from christianwhocodes.generators import FileGeneratorOption
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class FileOption(StrEnum):
|
|
7
|
+
PG_SERVICE = FileGeneratorOption.PG_SERVICE.value
|
|
8
|
+
PGPASS = FileGeneratorOption.PGPASS.value
|
|
9
|
+
SSH_CONFIG = FileGeneratorOption.SSH_CONFIG.value
|
|
10
|
+
ENV = "env"
|
|
11
|
+
SERVER = "server"
|
|
12
|
+
VERCEL = "vercel"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class PresetType(StrEnum):
|
|
16
|
+
"""Available project presets."""
|
|
17
|
+
|
|
18
|
+
DEFAULT = "default"
|
|
19
|
+
VERCEL = "vercel"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Apps(StrEnum):
|
|
23
|
+
"""Django applications enumeration."""
|
|
24
|
+
|
|
25
|
+
ADMIN = "django.contrib.admin"
|
|
26
|
+
AUTH = "django.contrib.auth"
|
|
27
|
+
CONTENTTYPES = "django.contrib.contenttypes"
|
|
28
|
+
SESSIONS = "django.contrib.sessions"
|
|
29
|
+
MESSAGES = "django.contrib.messages"
|
|
30
|
+
STATICFILES = "django.contrib.staticfiles"
|
|
31
|
+
HTTP_COMPRESSION = "django_http_compression"
|
|
32
|
+
MINIFY_HTML = "django_minify_html"
|
|
33
|
+
BROWSER_RELOAD = "django_browser_reload"
|
|
34
|
+
WATCHFILES = "django_watchfiles"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class ContextProcessors(StrEnum):
|
|
38
|
+
"""Django template context processors enumeration."""
|
|
39
|
+
|
|
40
|
+
DEBUG = "django.template.context_processors.debug"
|
|
41
|
+
REQUEST = "django.template.context_processors.request"
|
|
42
|
+
AUTH = "django.contrib.auth.context_processors.auth"
|
|
43
|
+
MESSAGES = "django.contrib.messages.context_processors.messages"
|
|
44
|
+
CSP = "django.template.context_processors.csp"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class Middlewares(StrEnum):
|
|
48
|
+
"""Django middleware enumeration."""
|
|
49
|
+
|
|
50
|
+
SECURITY = "django.middleware.security.SecurityMiddleware"
|
|
51
|
+
SESSION = "django.contrib.sessions.middleware.SessionMiddleware"
|
|
52
|
+
COMMON = "django.middleware.common.CommonMiddleware"
|
|
53
|
+
CSRF = "django.middleware.csrf.CsrfViewMiddleware"
|
|
54
|
+
AUTH = "django.contrib.auth.middleware.AuthenticationMiddleware"
|
|
55
|
+
MESSAGES = "django.contrib.messages.middleware.MessageMiddleware"
|
|
56
|
+
CLICKJACKING = "django.middleware.clickjacking.XFrameOptionsMiddleware"
|
|
57
|
+
CSP = "django.middleware.csp.ContentSecurityPolicyMiddleware"
|
|
58
|
+
HTTP_COMPRESSION = "django_http_compression.middleware.HttpCompressionMiddleware"
|
|
59
|
+
MINIFY_HTML = "django_minify_html.middleware.MinifyHtmlMiddleware"
|
|
60
|
+
BROWSER_RELOAD = "django_browser_reload.middleware.BrowserReloadMiddleware"
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
__all__: list[str] = ["FileOption", "PresetType", "Apps", "ContextProcessors", "Middlewares"]
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
"""# DjangX settings: Installed apps, middleware, templates, context processors."""
|
|
2
|
-
# TODO: Refactor so that the user can specify the order of apps, middleware, and context processors more flexibly.
|
|
3
|
-
|
|
4
|
-
from enum import StrEnum
|
|
5
|
-
|
|
6
1
|
from ... import (
|
|
7
2
|
INCLUDE_PROJECT_MAIN_APP,
|
|
8
3
|
PKG_API_NAME,
|
|
@@ -13,28 +8,16 @@ from ... import (
|
|
|
13
8
|
Conf,
|
|
14
9
|
ConfField,
|
|
15
10
|
)
|
|
11
|
+
from ..enums import Apps, ContextProcessors, Middlewares
|
|
16
12
|
from ..types import TemplatesDict
|
|
17
13
|
|
|
14
|
+
# TODO: Refactor so that the user can specify the order of apps, middleware, and context processors more flexibly.
|
|
15
|
+
|
|
18
16
|
# ===============================================================
|
|
19
17
|
# Apps
|
|
20
18
|
# ===============================================================
|
|
21
19
|
|
|
22
20
|
|
|
23
|
-
class _Apps(StrEnum):
|
|
24
|
-
"""Django applications enumeration."""
|
|
25
|
-
|
|
26
|
-
ADMIN = "django.contrib.admin"
|
|
27
|
-
AUTH = "django.contrib.auth"
|
|
28
|
-
CONTENTTYPES = "django.contrib.contenttypes"
|
|
29
|
-
SESSIONS = "django.contrib.sessions"
|
|
30
|
-
MESSAGES = "django.contrib.messages"
|
|
31
|
-
STATICFILES = "django.contrib.staticfiles"
|
|
32
|
-
HTTP_COMPRESSION = "django_http_compression"
|
|
33
|
-
MINIFY_HTML = "django_minify_html"
|
|
34
|
-
BROWSER_RELOAD = "django_browser_reload"
|
|
35
|
-
WATCHFILES = "django_watchfiles"
|
|
36
|
-
|
|
37
|
-
|
|
38
21
|
class AppsConf(Conf):
|
|
39
22
|
"""Apps configuration settings."""
|
|
40
23
|
|
|
@@ -70,19 +53,19 @@ def _get_installed_apps() -> list[str]:
|
|
|
70
53
|
]
|
|
71
54
|
|
|
72
55
|
third_party_apps: list[str] = [
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
56
|
+
Apps.HTTP_COMPRESSION,
|
|
57
|
+
Apps.MINIFY_HTML,
|
|
58
|
+
Apps.BROWSER_RELOAD,
|
|
59
|
+
Apps.WATCHFILES,
|
|
77
60
|
]
|
|
78
61
|
|
|
79
62
|
django_apps: list[str] = [
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
63
|
+
Apps.ADMIN,
|
|
64
|
+
Apps.AUTH,
|
|
65
|
+
Apps.CONTENTTYPES,
|
|
66
|
+
Apps.SESSIONS,
|
|
67
|
+
Apps.MESSAGES,
|
|
68
|
+
Apps.STATICFILES,
|
|
86
69
|
]
|
|
87
70
|
|
|
88
71
|
# Collect apps that should be removed except for base apps
|
|
@@ -107,19 +90,9 @@ INSTALLED_APPS: list[str] = _get_installed_apps()
|
|
|
107
90
|
# ===============================================================
|
|
108
91
|
|
|
109
92
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
DEBUG = "django.template.context_processors.debug"
|
|
114
|
-
REQUEST = "django.template.context_processors.request"
|
|
115
|
-
AUTH = "django.contrib.auth.context_processors.auth"
|
|
116
|
-
MESSAGES = "django.contrib.messages.context_processors.messages"
|
|
117
|
-
CSP = "django.template.context_processors.csp"
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
_APP_CONTEXT_PROCESSOR_MAP: dict[_Apps, list[_ContextProcessors]] = {
|
|
121
|
-
_Apps.AUTH: [_ContextProcessors.AUTH],
|
|
122
|
-
_Apps.MESSAGES: [_ContextProcessors.MESSAGES],
|
|
93
|
+
_APP_CONTEXT_PROCESSOR_MAP: dict[Apps, list[ContextProcessors]] = {
|
|
94
|
+
Apps.AUTH: [ContextProcessors.AUTH],
|
|
95
|
+
Apps.MESSAGES: [ContextProcessors.MESSAGES],
|
|
123
96
|
}
|
|
124
97
|
|
|
125
98
|
|
|
@@ -151,11 +124,11 @@ def _get_context_processors(installed_apps: list[str]) -> list[str]:
|
|
|
151
124
|
"""
|
|
152
125
|
# Django context processors in recommended order
|
|
153
126
|
django_context_processors: list[str] = [
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
127
|
+
ContextProcessors.DEBUG, # Debug info (only in DEBUG mode)
|
|
128
|
+
ContextProcessors.REQUEST, # Adds request object to context
|
|
129
|
+
ContextProcessors.AUTH, # Adds user and perms to context
|
|
130
|
+
ContextProcessors.MESSAGES, # Adds messages to context
|
|
131
|
+
ContextProcessors.CSP, # Content Security Policy
|
|
159
132
|
]
|
|
160
133
|
|
|
161
134
|
# Collect context processors that should be removed based on missing apps
|
|
@@ -193,29 +166,13 @@ TEMPLATES: TemplatesDict = [
|
|
|
193
166
|
# ===============================================================
|
|
194
167
|
|
|
195
168
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
AUTH = "django.contrib.auth.middleware.AuthenticationMiddleware"
|
|
204
|
-
MESSAGES = "django.contrib.messages.middleware.MessageMiddleware"
|
|
205
|
-
CLICKJACKING = "django.middleware.clickjacking.XFrameOptionsMiddleware"
|
|
206
|
-
CSP = "django.middleware.csp.ContentSecurityPolicyMiddleware"
|
|
207
|
-
HTTP_COMPRESSION = "django_http_compression.middleware.HttpCompressionMiddleware"
|
|
208
|
-
MINIFY_HTML = "django_minify_html.middleware.MinifyHtmlMiddleware"
|
|
209
|
-
BROWSER_RELOAD = "django_browser_reload.middleware.BrowserReloadMiddleware"
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
_APP_MIDDLEWARE_MAP: dict[_Apps, list[_Middlewares]] = {
|
|
213
|
-
_Apps.SESSIONS: [_Middlewares.SESSION],
|
|
214
|
-
_Apps.AUTH: [_Middlewares.AUTH],
|
|
215
|
-
_Apps.MESSAGES: [_Middlewares.MESSAGES],
|
|
216
|
-
_Apps.HTTP_COMPRESSION: [_Middlewares.HTTP_COMPRESSION],
|
|
217
|
-
_Apps.MINIFY_HTML: [_Middlewares.MINIFY_HTML],
|
|
218
|
-
_Apps.BROWSER_RELOAD: [_Middlewares.BROWSER_RELOAD],
|
|
169
|
+
_APP_MIDDLEWARE_MAP: dict[Apps, list[Middlewares]] = {
|
|
170
|
+
Apps.SESSIONS: [Middlewares.SESSION],
|
|
171
|
+
Apps.AUTH: [Middlewares.AUTH],
|
|
172
|
+
Apps.MESSAGES: [Middlewares.MESSAGES],
|
|
173
|
+
Apps.HTTP_COMPRESSION: [Middlewares.HTTP_COMPRESSION],
|
|
174
|
+
Apps.MINIFY_HTML: [Middlewares.MINIFY_HTML],
|
|
175
|
+
Apps.BROWSER_RELOAD: [Middlewares.BROWSER_RELOAD],
|
|
219
176
|
}
|
|
220
177
|
|
|
221
178
|
|
|
@@ -260,17 +217,17 @@ def _get_middleware(installed_apps: list[str]) -> list[str]:
|
|
|
260
217
|
- ABOVE any middleware that modifies HTML (like BrowserReloadMiddleware)
|
|
261
218
|
"""
|
|
262
219
|
django_middleware: list[str] = [
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
220
|
+
Middlewares.SECURITY, # FIRST - security headers, HTTPS redirect
|
|
221
|
+
Middlewares.SESSION, # Early - needed by auth & messages
|
|
222
|
+
Middlewares.COMMON, # Early - URL normalization
|
|
223
|
+
Middlewares.CSRF, # After session - needs session data
|
|
224
|
+
Middlewares.AUTH, # After session - stores user in session
|
|
225
|
+
Middlewares.MESSAGES, # After session & auth
|
|
226
|
+
Middlewares.CLICKJACKING, # Security headers (X-Frame-Options)
|
|
227
|
+
Middlewares.CSP, # Security headers (Content-Security-Policy)
|
|
228
|
+
Middlewares.HTTP_COMPRESSION, # Before minify - encodes responses (Zstandard, Brotli, Gzip)
|
|
229
|
+
Middlewares.MINIFY_HTML, # After compression, before HTML modifiers
|
|
230
|
+
Middlewares.BROWSER_RELOAD, # LAST - dev only, injects reload script into HTML
|
|
274
231
|
]
|
|
275
232
|
|
|
276
233
|
# Collect middleware that should be removed based on missing apps
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|