django-cfg 1.4.92__py3-none-any.whl → 1.4.93__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.
Potentially problematic release.
This version of django-cfg might be problematic. Click here for more details.
- django_cfg/__init__.py +1 -1
- django_cfg/apps/frontend/views.py +99 -16
- django_cfg/modules/nextjs_admin/views.py +24 -27
- django_cfg/pyproject.toml +3 -3
- django_cfg/static/frontend/nextjs_admin.zip +0 -0
- django_cfg/templates/admin/index.html +69 -7
- django_cfg/templatetags/django_cfg.py +7 -13
- {django_cfg-1.4.92.dist-info → django_cfg-1.4.93.dist-info}/METADATA +228 -171
- {django_cfg-1.4.92.dist-info → django_cfg-1.4.93.dist-info}/RECORD +12 -12
- {django_cfg-1.4.92.dist-info → django_cfg-1.4.93.dist-info}/WHEEL +0 -0
- {django_cfg-1.4.92.dist-info → django_cfg-1.4.93.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.4.92.dist-info → django_cfg-1.4.93.dist-info}/licenses/LICENSE +0 -0
django_cfg/__init__.py
CHANGED
|
@@ -4,14 +4,17 @@ JWT tokens are automatically injected into HTML responses for authenticated user
|
|
|
4
4
|
This is specific to Next.js frontend apps only.
|
|
5
5
|
|
|
6
6
|
Features:
|
|
7
|
-
- Automatic extraction of ZIP archives
|
|
7
|
+
- Automatic extraction of ZIP archives with smart update detection
|
|
8
|
+
- Auto-reextraction when ZIP is modified within last 5 minutes
|
|
8
9
|
- SPA routing with fallback strategies
|
|
9
10
|
- JWT token injection for authenticated users
|
|
10
11
|
"""
|
|
11
12
|
|
|
12
13
|
import logging
|
|
13
14
|
import zipfile
|
|
15
|
+
import shutil
|
|
14
16
|
from pathlib import Path
|
|
17
|
+
from datetime import datetime, timedelta
|
|
15
18
|
from django.http import Http404, HttpResponse, FileResponse
|
|
16
19
|
from django.views.static import serve
|
|
17
20
|
from django.views import View
|
|
@@ -23,19 +26,105 @@ from rest_framework_simplejwt.tokens import RefreshToken
|
|
|
23
26
|
logger = logging.getLogger(__name__)
|
|
24
27
|
|
|
25
28
|
|
|
29
|
+
class ZipExtractionMixin:
|
|
30
|
+
"""
|
|
31
|
+
Mixin for automatic ZIP extraction with smart update detection.
|
|
32
|
+
|
|
33
|
+
Provides intelligent ZIP archive handling:
|
|
34
|
+
- Auto-extraction when directory doesn't exist
|
|
35
|
+
- Auto-reextraction when ZIP is modified < 5 minutes ago
|
|
36
|
+
- Timestamp comparison to avoid unnecessary extractions
|
|
37
|
+
|
|
38
|
+
Usage:
|
|
39
|
+
class MyView(ZipExtractionMixin, View):
|
|
40
|
+
app_name = 'myapp' # Will look for myapp.zip
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def extract_zip_if_needed(self, base_dir: Path, zip_path: Path, app_name: str) -> bool:
|
|
44
|
+
"""
|
|
45
|
+
Extract ZIP archive if needed based on modification times.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
base_dir: Target directory for extraction
|
|
49
|
+
zip_path: Path to ZIP archive
|
|
50
|
+
app_name: Name of the app (for logging)
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
bool: True if extraction succeeded or not needed, False if failed
|
|
54
|
+
"""
|
|
55
|
+
should_extract = False
|
|
56
|
+
|
|
57
|
+
# Priority 1: If directory doesn't exist at all - always extract
|
|
58
|
+
if not base_dir.exists():
|
|
59
|
+
should_extract = True
|
|
60
|
+
logger.info(f"[{app_name}] Directory {base_dir} doesn't exist, will extract")
|
|
61
|
+
|
|
62
|
+
# Priority 2: Directory exists - check if ZIP is fresh and needs update
|
|
63
|
+
elif zip_path.exists():
|
|
64
|
+
# Get ZIP modification time
|
|
65
|
+
zip_mtime = datetime.fromtimestamp(zip_path.stat().st_mtime)
|
|
66
|
+
time_since_modification = (datetime.now() - zip_mtime).total_seconds()
|
|
67
|
+
|
|
68
|
+
# If ZIP was modified less than 5 minutes ago - check for updates
|
|
69
|
+
if time_since_modification < 300: # 5 minutes = 300 seconds
|
|
70
|
+
# Compare ZIP time with directory time
|
|
71
|
+
dir_mtime = datetime.fromtimestamp(base_dir.stat().st_mtime)
|
|
72
|
+
|
|
73
|
+
# If ZIP is newer than directory, re-extract
|
|
74
|
+
if zip_mtime > dir_mtime:
|
|
75
|
+
logger.info(f"[{app_name}] ZIP is fresh ({time_since_modification:.0f}s) and newer, re-extracting")
|
|
76
|
+
try:
|
|
77
|
+
shutil.rmtree(base_dir)
|
|
78
|
+
should_extract = True
|
|
79
|
+
except Exception as e:
|
|
80
|
+
logger.error(f"[{app_name}] Failed to remove old directory: {e}")
|
|
81
|
+
return False
|
|
82
|
+
else:
|
|
83
|
+
logger.debug(f"[{app_name}] ZIP is fresh but directory is up-to-date")
|
|
84
|
+
else:
|
|
85
|
+
# ZIP is old (> 5 min) - use existing directory, no checks needed
|
|
86
|
+
logger.debug(f"[{app_name}] ZIP is old ({time_since_modification:.0f}s), using existing")
|
|
87
|
+
|
|
88
|
+
# Extract ZIP if needed
|
|
89
|
+
if should_extract:
|
|
90
|
+
if zip_path.exists():
|
|
91
|
+
logger.info(f"[{app_name}] Extracting {zip_path.name} to {base_dir}...")
|
|
92
|
+
try:
|
|
93
|
+
base_dir.parent.mkdir(parents=True, exist_ok=True)
|
|
94
|
+
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
|
|
95
|
+
zip_ref.extractall(base_dir)
|
|
96
|
+
logger.info(f"[{app_name}] Successfully extracted {zip_path.name}")
|
|
97
|
+
return True
|
|
98
|
+
except Exception as e:
|
|
99
|
+
logger.error(f"[{app_name}] Failed to extract: {e}")
|
|
100
|
+
return False
|
|
101
|
+
else:
|
|
102
|
+
logger.error(f"[{app_name}] ZIP not found: {zip_path}")
|
|
103
|
+
return False
|
|
104
|
+
|
|
105
|
+
# Directory exists and is up-to-date
|
|
106
|
+
return True
|
|
107
|
+
|
|
108
|
+
|
|
26
109
|
@method_decorator(xframe_options_exempt, name='dispatch')
|
|
27
|
-
class NextJSStaticView(View):
|
|
110
|
+
class NextJSStaticView(ZipExtractionMixin, View):
|
|
28
111
|
"""
|
|
29
112
|
Serve Next.js static build files with automatic JWT token injection.
|
|
30
113
|
|
|
31
114
|
Features:
|
|
32
115
|
- Serves Next.js static export files like a static file server
|
|
116
|
+
- Smart ZIP extraction: auto-updates when archive modified < 5 minutes ago
|
|
33
117
|
- Automatically injects JWT tokens for authenticated users
|
|
34
118
|
- Tokens injected into HTML responses only
|
|
35
119
|
- Handles Next.js client-side routing (.html fallback)
|
|
36
120
|
- Automatically serves index.html for directory paths
|
|
37
121
|
- X-Frame-Options exempt to allow embedding in iframes
|
|
38
122
|
|
|
123
|
+
ZIP Update Logic:
|
|
124
|
+
- If ZIP modified < 5 minutes ago: removes old files and re-extracts
|
|
125
|
+
- If ZIP modified > 5 minutes ago: uses existing extraction
|
|
126
|
+
- This enables hot-reload during development while staying fast in production
|
|
127
|
+
|
|
39
128
|
Path resolution examples:
|
|
40
129
|
- /cfg/admin/ → /cfg/admin/index.html
|
|
41
130
|
- /cfg/admin/private/ → /cfg/admin/private/index.html (if exists)
|
|
@@ -51,22 +140,16 @@ class NextJSStaticView(View):
|
|
|
51
140
|
import django_cfg
|
|
52
141
|
|
|
53
142
|
base_dir = Path(django_cfg.__file__).parent / 'static' / 'frontend' / self.app_name
|
|
143
|
+
zip_path = Path(django_cfg.__file__).parent / 'static' / 'frontend' / f'{self.app_name}.zip'
|
|
54
144
|
|
|
55
|
-
#
|
|
145
|
+
# Extract ZIP if needed using mixin
|
|
146
|
+
if not self.extract_zip_if_needed(base_dir, zip_path, self.app_name):
|
|
147
|
+
return render(request, 'frontend/404.html', status=404)
|
|
148
|
+
|
|
149
|
+
# Ensure directory exists
|
|
56
150
|
if not base_dir.exists():
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
logger.info(f"Extracting {self.app_name}.zip to {base_dir}...")
|
|
60
|
-
try:
|
|
61
|
-
base_dir.parent.mkdir(parents=True, exist_ok=True)
|
|
62
|
-
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
|
|
63
|
-
zip_ref.extractall(base_dir)
|
|
64
|
-
logger.info(f"Successfully extracted {self.app_name}.zip")
|
|
65
|
-
except Exception as e:
|
|
66
|
-
logger.error(f"Failed to extract {self.app_name}.zip: {e}")
|
|
67
|
-
return render(request, 'frontend/404.html', status=404)
|
|
68
|
-
else:
|
|
69
|
-
return render(request, 'frontend/404.html', status=404)
|
|
151
|
+
logger.error(f"[{self.app_name}] Directory doesn't exist after extraction attempt")
|
|
152
|
+
return render(request, 'frontend/404.html', status=404)
|
|
70
153
|
|
|
71
154
|
original_path = path # Store for logging
|
|
72
155
|
|
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
Views for Next.js admin integration.
|
|
3
3
|
|
|
4
4
|
Serves Next.js static files with SPA routing support and JWT injection.
|
|
5
|
+
|
|
6
|
+
Features:
|
|
7
|
+
- Automatic extraction of ZIP archives with smart update detection
|
|
8
|
+
- Auto-reextraction when ZIP is modified within last 5 minutes
|
|
9
|
+
- SPA routing with fallback strategies
|
|
10
|
+
- JWT token injection for authenticated users
|
|
5
11
|
"""
|
|
6
12
|
|
|
7
13
|
import logging
|
|
@@ -13,12 +19,13 @@ from django.views.decorators.clickjacking import xframe_options_exempt
|
|
|
13
19
|
from django.utils.decorators import method_decorator
|
|
14
20
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
|
15
21
|
from django.shortcuts import render
|
|
22
|
+
from django_cfg.apps.frontend.views import ZipExtractionMixin
|
|
16
23
|
|
|
17
24
|
logger = logging.getLogger(__name__)
|
|
18
25
|
|
|
19
26
|
|
|
20
27
|
@method_decorator(xframe_options_exempt, name='dispatch')
|
|
21
|
-
class NextJsAdminView(LoginRequiredMixin, View):
|
|
28
|
+
class NextJsAdminView(ZipExtractionMixin, LoginRequiredMixin, View):
|
|
22
29
|
"""
|
|
23
30
|
Serve Next.js admin panel with JWT injection and SPA routing.
|
|
24
31
|
|
|
@@ -38,7 +45,6 @@ class NextJsAdminView(LoginRequiredMixin, View):
|
|
|
38
45
|
"""Serve Next.js files with JWT injection and SPA routing."""
|
|
39
46
|
from django_cfg.core.config import get_current_config
|
|
40
47
|
import django_cfg
|
|
41
|
-
import zipfile
|
|
42
48
|
|
|
43
49
|
config = get_current_config()
|
|
44
50
|
if not config or not config.nextjs_admin:
|
|
@@ -48,33 +54,24 @@ class NextJsAdminView(LoginRequiredMixin, View):
|
|
|
48
54
|
|
|
49
55
|
# Use extracted directory from static/frontend/nextjs_admin/
|
|
50
56
|
base_dir = Path(django_cfg.__file__).parent / 'static' / 'frontend' / 'nextjs_admin'
|
|
57
|
+
zip_path = Path(django_cfg.__file__).parent / 'static' / 'frontend' / 'nextjs_admin.zip'
|
|
58
|
+
|
|
59
|
+
# Fallback: Try solution project static directory
|
|
60
|
+
if not zip_path.exists():
|
|
61
|
+
from django.conf import settings
|
|
62
|
+
solution_zip = Path(settings.BASE_DIR) / 'static' / 'nextjs_admin.zip'
|
|
63
|
+
if solution_zip.exists():
|
|
64
|
+
zip_path = solution_zip
|
|
65
|
+
logger.info(f"[nextjs_admin] Using ZIP from solution project: {solution_zip}")
|
|
51
66
|
|
|
52
|
-
#
|
|
67
|
+
# Extract ZIP if needed using mixin
|
|
68
|
+
if not self.extract_zip_if_needed(base_dir, zip_path, 'nextjs_admin'):
|
|
69
|
+
return render(request, 'frontend/404.html', status=404)
|
|
70
|
+
|
|
71
|
+
# Ensure directory exists
|
|
53
72
|
if not base_dir.exists():
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
# Fallback: Try solution project static directory
|
|
58
|
-
if not zip_path.exists():
|
|
59
|
-
from django.conf import settings
|
|
60
|
-
solution_zip = Path(settings.BASE_DIR) / 'static' / 'nextjs_admin.zip'
|
|
61
|
-
if solution_zip.exists():
|
|
62
|
-
zip_path = solution_zip
|
|
63
|
-
logger.info(f"Using ZIP from solution project: {solution_zip}")
|
|
64
|
-
|
|
65
|
-
if zip_path.exists():
|
|
66
|
-
logger.info(f"Extracting nextjs_admin.zip to {base_dir}...")
|
|
67
|
-
try:
|
|
68
|
-
base_dir.parent.mkdir(parents=True, exist_ok=True)
|
|
69
|
-
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
|
|
70
|
-
zip_ref.extractall(base_dir)
|
|
71
|
-
logger.info(f"Successfully extracted nextjs_admin.zip from {zip_path}")
|
|
72
|
-
except Exception as e:
|
|
73
|
-
logger.error(f"Failed to extract nextjs_admin.zip: {e}")
|
|
74
|
-
return render(request, 'frontend/404.html', status=404)
|
|
75
|
-
else:
|
|
76
|
-
logger.error(f"nextjs_admin.zip not found in django_cfg or solution project")
|
|
77
|
-
return render(request, 'frontend/404.html', status=404)
|
|
73
|
+
logger.error(f"[nextjs_admin] Directory doesn't exist after extraction attempt")
|
|
74
|
+
return render(request, 'frontend/404.html', status=404)
|
|
78
75
|
|
|
79
76
|
static_dir = base_dir
|
|
80
77
|
|
django_cfg/pyproject.toml
CHANGED
|
@@ -4,10 +4,10 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "django-cfg"
|
|
7
|
-
version = "1.4.
|
|
8
|
-
description = "Django
|
|
7
|
+
version = "1.4.93"
|
|
8
|
+
description = "Modern Django framework with type-safe Pydantic v2 configuration, Next.js admin integration, real-time WebSockets, and 8 enterprise apps. Replace settings.py with validated models, 90% less code. Production-ready with AI agents, auto-generated TypeScript clients, and zero-config features."
|
|
9
9
|
readme = "README.md"
|
|
10
|
-
keywords = [ "django", "configuration", "pydantic", "settings", "type-safety", "pydantic-settings", "django-environ", "startup-validation", "ide-autocomplete", "ai-agents", "enterprise-django", "django-settings", "type-safe-config",]
|
|
10
|
+
keywords = [ "django", "configuration", "pydantic", "settings", "type-safety", "pydantic-settings", "django-environ", "startup-validation", "ide-autocomplete", "nextjs-admin", "react-admin", "websocket", "centrifugo", "real-time", "typescript-generation", "ai-agents", "enterprise-django", "django-settings", "type-safe-config", "modern-django",]
|
|
11
11
|
classifiers = [ "Development Status :: 4 - Beta", "Framework :: Django", "Framework :: Django :: 5.2", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: Dynamic Content", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: System :: Systems Administration", "Typing :: Typed",]
|
|
12
12
|
requires-python = ">=3.12,<3.14"
|
|
13
13
|
dependencies = [ "pydantic>=2.11.0,<3.0", "pydantic[email]>=2.11.0,<3.0", "PyYAML>=6.0,<7.0", "click>=8.2.0,<9.0", "questionary>=2.1.0,<3.0", "rich>=14.0.0,<15.0", "cloudflare>=4.3.0,<5.0", "loguru>=0.7.0,<1.0", "colorlog>=6.9.0,<7.0", "cachetools>=5.3.0,<7.0", "toml>=0.10.2,<0.11.0", "ngrok>=1.5.1; python_version>='3.12'", "psycopg[binary,pool]>=3.2.0,<4.0", "dj-database-url>=3.0.0,<4.0", "whitenoise>=6.8.0,<7.0", "django-cors-headers>=4.7.0,<5.0", "djangorestframework>=3.16.0,<4.0", "djangorestframework-simplejwt>=5.5.0,<6.0", "djangorestframework-simplejwt[token-blacklist]>=5.5.0,<6.0", "drf-nested-routers>=0.94.0,<1.0", "django-filter>=25.0,<26.0", "django-ratelimit>=4.1.0,<5.0.0", "drf-spectacular>=0.28.0,<1.0", "drf-spectacular-sidecar>=2025.8.0,<2026.0", "django-json-widget>=2.0.0,<3.0", "django-import-export>=4.3.0,<5.0", "django-extensions>=4.1.0,<5.0", "django-constance>=4.3.0,<5.0", "django-unfold>=0.64.0,<1.0", "django-redis>=6.0.0,<7.0", "redis>=6.4.0,<7.0", "hiredis>=2.0.0,<4.0", "dramatiq[redis]>=1.18.0,<2.0", "django-dramatiq>=0.14.0,<1.0", "pyTelegramBotAPI>=4.28.0,<5.0", "coolname>=2.2.0,<3.0", "django-admin-rangefilter>=0.13.0,<1.0", "python-json-logger>=3.3.0,<4.0", "requests>=2.32.0,<3.0", "tiktoken>=0.11.0,<1.0", "openai>=1.107.0,<2.0", "twilio>=9.8.0,<10.0", "sendgrid>=6.12.0,<7.0", "beautifulsoup4>=4.13.0,<5.0", "lxml>=6.0.0,<7.0", "pgvector>=0.4.0,<1.0", "tenacity>=9.1.2,<10.0.0", "mypy (>=1.18.2,<2.0.0)", "django-tailwind[reload] (>=4.2.0,<5.0.0)", "jinja2 (>=3.1.6,<4.0.0)", "django-axes[ipware] (>=8.0.0,<9.0.0)", "pydantic-settings (>=2.11.0,<3.0.0)",]
|
|
Binary file
|
|
@@ -79,7 +79,27 @@
|
|
|
79
79
|
{% block content %}
|
|
80
80
|
<!-- Main Container -->
|
|
81
81
|
{% component "unfold/components/container.html" %}
|
|
82
|
-
<div x-data="{
|
|
82
|
+
<div x-data="{
|
|
83
|
+
activeTab: 'builtin',
|
|
84
|
+
previousTab: 'builtin',
|
|
85
|
+
switchTab(tab) {
|
|
86
|
+
if (this.previousTab !== tab) {
|
|
87
|
+
// Reset iframe to initial URL when switching tabs
|
|
88
|
+
this.resetIframe(this.previousTab);
|
|
89
|
+
this.previousTab = tab;
|
|
90
|
+
}
|
|
91
|
+
this.activeTab = tab;
|
|
92
|
+
},
|
|
93
|
+
resetIframe(tab) {
|
|
94
|
+
// Reset the iframe that was just hidden
|
|
95
|
+
const iframeId = tab === 'builtin' ? 'nextjs-dashboard-iframe-builtin' : 'nextjs-dashboard-iframe-nextjs';
|
|
96
|
+
const iframe = document.getElementById(iframeId);
|
|
97
|
+
if (iframe) {
|
|
98
|
+
const originalSrc = iframe.getAttribute('data-original-src') || iframe.src;
|
|
99
|
+
iframe.src = originalSrc;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}">
|
|
83
103
|
{% if is_frontend_dev_mode %}
|
|
84
104
|
<!-- Development Mode Badge -->
|
|
85
105
|
<div class="bg-yellow-100 dark:bg-yellow-900 border-l-4 border-yellow-500 text-yellow-700 dark:text-yellow-300 p-4 mb-4" role="alert">
|
|
@@ -106,7 +126,7 @@
|
|
|
106
126
|
}
|
|
107
127
|
</style>
|
|
108
128
|
<nav class="-mb-px flex space-x-8 px-4" aria-label="Dashboard Tabs">
|
|
109
|
-
<button @click="
|
|
129
|
+
<button @click="switchTab('builtin')"
|
|
110
130
|
class="whitespace-nowrap py-4 px-2 border-b-2 font-medium text-sm flex items-center gap-2 transition-all duration-200"
|
|
111
131
|
:class="activeTab === 'builtin'
|
|
112
132
|
? 'border-primary-600 text-primary-600 dark:border-primary-500 dark:text-primary-500'
|
|
@@ -115,7 +135,7 @@
|
|
|
115
135
|
<span>Built-in Dashboard</span>
|
|
116
136
|
</button>
|
|
117
137
|
|
|
118
|
-
<button @click="
|
|
138
|
+
<button @click="switchTab('nextjs')"
|
|
119
139
|
class="whitespace-nowrap py-4 px-2 border-b-2 font-medium text-sm flex items-center gap-2 transition-all duration-200"
|
|
120
140
|
:class="activeTab === 'nextjs'
|
|
121
141
|
? 'border-primary-600 text-primary-600 dark:border-primary-500 dark:text-primary-500'
|
|
@@ -140,6 +160,7 @@
|
|
|
140
160
|
id="nextjs-dashboard-iframe-builtin"
|
|
141
161
|
class="nextjs-dashboard-iframe"
|
|
142
162
|
src="{% nextjs_admin_url %}"
|
|
163
|
+
data-original-src="{% nextjs_admin_url %}"
|
|
143
164
|
title="Next.js Dashboard"
|
|
144
165
|
sandbox="allow-same-origin allow-scripts allow-forms allow-popups allow-modals allow-storage-access-by-user-activation"
|
|
145
166
|
scrolling="no"
|
|
@@ -160,6 +181,7 @@
|
|
|
160
181
|
id="nextjs-dashboard-iframe-nextjs"
|
|
161
182
|
class="nextjs-dashboard-iframe nextjs-external-iframe"
|
|
162
183
|
src="{% nextjs_external_admin_url %}"
|
|
184
|
+
data-original-src="{% nextjs_external_admin_url %}"
|
|
163
185
|
title="{% nextjs_external_admin_title %}"
|
|
164
186
|
sandbox="allow-same-origin allow-scripts allow-forms allow-popups allow-modals allow-storage-access-by-user-activation"
|
|
165
187
|
scrolling="no"
|
|
@@ -225,22 +247,62 @@
|
|
|
225
247
|
|
|
226
248
|
// Debounce timer for resize events (prevents jittery iframe height changes)
|
|
227
249
|
let resizeTimer = null;
|
|
250
|
+
let loadTimeout = null;
|
|
228
251
|
|
|
229
252
|
// iframe load event
|
|
230
253
|
iframe.addEventListener('load', function() {
|
|
254
|
+
// Clear the fallback timeout
|
|
255
|
+
if (loadTimeout) {
|
|
256
|
+
clearTimeout(loadTimeout);
|
|
257
|
+
loadTimeout = null;
|
|
258
|
+
}
|
|
259
|
+
|
|
231
260
|
setTimeout(() => {
|
|
232
261
|
sendDataToIframe();
|
|
233
262
|
}, 100);
|
|
234
263
|
});
|
|
235
264
|
|
|
236
|
-
//
|
|
237
|
-
|
|
238
|
-
|
|
265
|
+
// iframe error event - fallback to static files
|
|
266
|
+
iframe.addEventListener('error', function() {
|
|
267
|
+
console.warn('[Django-CFG] iframe failed to load, falling back to static files');
|
|
268
|
+
handleLoadFailure();
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
// Timeout fallback - if iframe doesn't load in 3 seconds
|
|
272
|
+
loadTimeout = setTimeout(() => {
|
|
273
|
+
if (!iframe.classList.contains('loaded') && isDevMode) {
|
|
274
|
+
console.warn('[Django-CFG] Dev server timeout, falling back to static files');
|
|
275
|
+
handleLoadFailure();
|
|
276
|
+
} else if (!iframe.classList.contains('loaded')) {
|
|
239
277
|
console.log('[Django-CFG] Timeout - showing iframe anyway');
|
|
240
278
|
if (loading) loading.classList.add('hidden');
|
|
241
279
|
iframe.classList.add('loaded');
|
|
242
280
|
}
|
|
243
|
-
},
|
|
281
|
+
}, 3000);
|
|
282
|
+
|
|
283
|
+
// Handle load failure - switch to static files
|
|
284
|
+
function handleLoadFailure() {
|
|
285
|
+
if (!isDevMode) return; // Already using static files
|
|
286
|
+
|
|
287
|
+
const originalSrc = iframe.getAttribute('data-original-src');
|
|
288
|
+
if (!originalSrc) return;
|
|
289
|
+
|
|
290
|
+
// Extract path from dev server URL
|
|
291
|
+
const devUrl = new URL(originalSrc);
|
|
292
|
+
const pathPart = devUrl.pathname.replace('/admin', '').replace(/^\//, '');
|
|
293
|
+
|
|
294
|
+
// Build static files URL
|
|
295
|
+
const staticUrl = `/cfg/admin/admin/${pathPart}`;
|
|
296
|
+
|
|
297
|
+
console.log('[Django-CFG] Switching to static files:', staticUrl);
|
|
298
|
+
iframe.src = staticUrl;
|
|
299
|
+
|
|
300
|
+
// Show iframe after switching
|
|
301
|
+
setTimeout(() => {
|
|
302
|
+
if (loading) loading.classList.add('hidden');
|
|
303
|
+
iframe.classList.add('loaded');
|
|
304
|
+
}, 500);
|
|
305
|
+
}
|
|
244
306
|
|
|
245
307
|
// Send theme and auth data to iframe
|
|
246
308
|
function sendDataToIframe() {
|
|
@@ -170,11 +170,10 @@ def nextjs_admin_url(path=''):
|
|
|
170
170
|
Get the URL for Next.js Admin Panel (Built-in Dashboard - Tab 1).
|
|
171
171
|
|
|
172
172
|
Auto-detects development mode with priority:
|
|
173
|
-
1. If port
|
|
174
|
-
2.
|
|
175
|
-
3. Otherwise → /cfg/admin/admin/{path} (static files)
|
|
173
|
+
1. If port 3001 is available → http://localhost:3001/admin/{path} (dev server)
|
|
174
|
+
2. Otherwise → /cfg/admin/admin/{path} (static files)
|
|
176
175
|
|
|
177
|
-
|
|
176
|
+
Note: Port 3000 is reserved for external Next.js admin (Tab 2).
|
|
178
177
|
|
|
179
178
|
Usage in template:
|
|
180
179
|
{% load django_cfg %}
|
|
@@ -188,20 +187,15 @@ def nextjs_admin_url(path=''):
|
|
|
188
187
|
# Production mode: always use static files
|
|
189
188
|
return f'/cfg/admin/admin/{path}' if path else '/cfg/admin/admin/'
|
|
190
189
|
|
|
191
|
-
# Check
|
|
192
|
-
port_3000_available = _is_port_available('localhost', 3000)
|
|
190
|
+
# Check if port 3001 is available for Tab 1 (built-in admin)
|
|
193
191
|
port_3001_available = _is_port_available('localhost', 3001)
|
|
194
192
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
# Solution project is running - use static files for Tab 1
|
|
198
|
-
return f'/cfg/admin/admin/{path}' if path else '/cfg/admin/admin/'
|
|
199
|
-
elif port_3001_available:
|
|
200
|
-
# Dev project is running - use dev server for Tab 1
|
|
193
|
+
if port_3001_available:
|
|
194
|
+
# Dev server is running on 3001 - use it
|
|
201
195
|
base_url = 'http://localhost:3001/admin'
|
|
202
196
|
return f'{base_url}/{path}' if path else base_url
|
|
203
197
|
else:
|
|
204
|
-
# No dev server - use static files
|
|
198
|
+
# No dev server or dev server stopped - use static files
|
|
205
199
|
return f'/cfg/admin/admin/{path}' if path else '/cfg/admin/admin/'
|
|
206
200
|
|
|
207
201
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: django-cfg
|
|
3
|
-
Version: 1.4.
|
|
4
|
-
Summary: Django
|
|
3
|
+
Version: 1.4.93
|
|
4
|
+
Summary: Modern Django framework with type-safe Pydantic v2 configuration, Next.js admin integration, real-time WebSockets, and 8 enterprise apps. Replace settings.py with validated models, 90% less code. Production-ready with AI agents, auto-generated TypeScript clients, and zero-config features.
|
|
5
5
|
Project-URL: Homepage, https://djangocfg.com
|
|
6
6
|
Project-URL: Documentation, https://djangocfg.com
|
|
7
7
|
Project-URL: Repository, https://github.com/markolofsen/django-cfg
|
|
@@ -11,7 +11,7 @@ Author-email: Django-CFG Team <info@djangocfg.com>
|
|
|
11
11
|
Maintainer-email: Django-CFG Team <info@djangocfg.com>
|
|
12
12
|
License: MIT
|
|
13
13
|
License-File: LICENSE
|
|
14
|
-
Keywords: ai-agents,configuration,django,django-environ,django-settings,enterprise-django,ide-autocomplete,pydantic,pydantic-settings,settings,startup-validation,type-safe-config,type-safety
|
|
14
|
+
Keywords: ai-agents,centrifugo,configuration,django,django-environ,django-settings,enterprise-django,ide-autocomplete,modern-django,nextjs-admin,pydantic,pydantic-settings,react-admin,real-time,settings,startup-validation,type-safe-config,type-safety,typescript-generation,websocket
|
|
15
15
|
Classifier: Development Status :: 4 - Beta
|
|
16
16
|
Classifier: Framework :: Django
|
|
17
17
|
Classifier: Framework :: Django :: 5.2
|
|
@@ -148,7 +148,7 @@ Requires-Dist: pytest-xdist<4.0,>=3.8; extra == 'test'
|
|
|
148
148
|
Requires-Dist: pytest<9.0,>=8.4; extra == 'test'
|
|
149
149
|
Description-Content-Type: text/markdown
|
|
150
150
|
|
|
151
|
-
# Django-CFG: Type-Safe Django Configuration Framework
|
|
151
|
+
# Django-CFG: Type-Safe Django Configuration Framework
|
|
152
152
|
|
|
153
153
|
[](https://www.python.org/downloads/)
|
|
154
154
|
[](https://www.djangoproject.com/)
|
|
@@ -165,119 +165,86 @@ Description-Content-Type: text/markdown
|
|
|
165
165
|
|
|
166
166
|
<div align="center">
|
|
167
167
|
|
|
168
|
-
### 🚀
|
|
168
|
+
### 🚀 The Modern Django Framework for Enterprise Applications
|
|
169
169
|
|
|
170
|
-
**Type-safe
|
|
170
|
+
**Type-safe configuration** • **Next.js Admin** • **AI Agents** • **Real-time WebSockets** • **8 Enterprise Apps**
|
|
171
171
|
|
|
172
|
-
**[
|
|
172
|
+
**[🎯 Live Demo](http://demo.djangocfg.com)** • **[📚 Documentation](https://djangocfg.com/docs/getting-started/intro)** • **[🐙 GitHub](https://github.com/markolofsen/django-cfg)**
|
|
173
173
|
|
|
174
174
|
</div>
|
|
175
175
|
|
|
176
|
-
## 🤖 AI Project Generator - Zero Setup Required
|
|
177
|
-
|
|
178
|
-
**Describe your app in plain English, get production-ready Django project in 30 seconds:**
|
|
179
|
-
|
|
180
|
-
> *"I need a SaaS app with user authentication, Stripe payments, and admin dashboard"*
|
|
181
|
-
|
|
182
|
-
**AI generates:** ✅ Type-safe config • ✅ Database models • ✅ REST API + docs • ✅ Modern UI • ✅ Deployment ready
|
|
183
|
-
|
|
184
|
-
### **[→ Try AI Editor Now](https://editor.djangocfg.com)**
|
|
185
|
-
|
|
186
176
|
---
|
|
187
177
|
|
|
188
|
-
## 🎯
|
|
178
|
+
## 🎯 What is Django-CFG?
|
|
189
179
|
|
|
190
|
-
**Django-CFG
|
|
180
|
+
**Django-CFG** is a next-generation Django framework that replaces traditional `settings.py` with **type-safe Pydantic v2 models**. It eliminates runtime configuration errors, provides complete IDE autocomplete, and includes **production-ready enterprise features** out of the box.
|
|
191
181
|
|
|
192
|
-
### Why
|
|
182
|
+
### Why Django-CFG?
|
|
193
183
|
|
|
194
|
-
**Traditional Django
|
|
195
|
-
- ❌ **Runtime errors** -
|
|
196
|
-
- ❌ **No IDE support** - zero autocomplete, manual
|
|
197
|
-
- ❌ **200+ lines**
|
|
198
|
-
- ❌ **
|
|
184
|
+
**Traditional Django problems:**
|
|
185
|
+
- ❌ **Runtime errors** - configuration bugs discovered in production
|
|
186
|
+
- ❌ **No IDE support** - zero autocomplete, manual documentation lookup
|
|
187
|
+
- ❌ **200+ lines** of unmaintainable settings.py
|
|
188
|
+
- ❌ **Weeks of setup** - for user auth, admin UI, payments, real-time features
|
|
199
189
|
|
|
200
|
-
**Django-CFG
|
|
201
|
-
- ✅ **
|
|
202
|
-
- ✅ **Full IDE autocomplete** - IntelliSense for
|
|
190
|
+
**Django-CFG solution:**
|
|
191
|
+
- ✅ **Startup validation** - catch all config errors before deployment
|
|
192
|
+
- ✅ **Full IDE autocomplete** - IntelliSense for every setting
|
|
203
193
|
- ✅ **30 lines of code** - 90% boilerplate reduction
|
|
204
|
-
- ✅ **
|
|
194
|
+
- ✅ **30 seconds to production** - everything included and ready
|
|
205
195
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
| Feature | settings.py | django-environ | pydantic-settings | **Django-CFG** |
|
|
209
|
-
|---------|-------------|----------------|-------------------|----------------|
|
|
210
|
-
| **Type Safety** | ❌ Runtime only | ⚠️ Basic casting | ✅ Pydantic | ✅ **Full Pydantic v2** |
|
|
211
|
-
| **IDE Autocomplete** | ❌ None | ❌ None | ⚠️ Partial | ✅ **100%** |
|
|
212
|
-
| **Startup Validation** | ❌ No | ⚠️ Partial | ✅ Yes | ✅ **Yes + Custom validators** |
|
|
213
|
-
| **Django Integration** | ✅ Native | ⚠️ Partial | ❌ Manual | ✅ **Seamless** |
|
|
214
|
-
| **Built-in Apps** | ❌ Build yourself | ❌ None | ❌ None | ✅ **8 enterprise apps** |
|
|
215
|
-
| **AI-Ready** | ❌ Manual setup | ❌ None | ❌ None | ✅ **LLM + Vector DB** |
|
|
216
|
-
|
|
217
|
-
**[📚 Full comparison guide →](https://djangocfg.com/docs/getting-started/django-cfg-vs-alternatives)**
|
|
196
|
+
**[📚 Read the full comparison →](https://djangocfg.com/docs/getting-started/django-cfg-vs-alternatives)**
|
|
218
197
|
|
|
219
198
|
---
|
|
220
199
|
|
|
221
|
-
## 🚀
|
|
222
|
-
|
|
223
|
-
### Option 1: AI Editor (Fastest - 30 seconds) ⚡
|
|
224
|
-
|
|
225
|
-
**Generate project with AI - no installation needed:**
|
|
200
|
+
## 🚀 Quick Start
|
|
226
201
|
|
|
227
|
-
|
|
228
|
-
2. Describe your app in plain English
|
|
229
|
-
3. Download ready-to-deploy project
|
|
230
|
-
|
|
231
|
-
**[→ Generate with AI](https://editor.djangocfg.com)**
|
|
232
|
-
|
|
233
|
-
---
|
|
234
|
-
|
|
235
|
-
### Option 2: Traditional CLI
|
|
202
|
+
### Installation
|
|
236
203
|
|
|
237
204
|
```bash
|
|
238
205
|
pip install django-cfg
|
|
239
|
-
django-cfg create-project "My
|
|
240
|
-
cd my-
|
|
206
|
+
django-cfg create-project "My App"
|
|
207
|
+
cd my-app && python manage.py runserver
|
|
241
208
|
```
|
|
242
209
|
|
|
243
210
|
**What you get instantly:**
|
|
244
|
-
- 🎨 Modern Admin UI → `http://127.0.0.1:8000/admin/`
|
|
245
|
-
-
|
|
246
|
-
-
|
|
211
|
+
- 🎨 **Modern Admin UI** → `http://127.0.0.1:8000/admin/`
|
|
212
|
+
- ⚡ **Next.js Dashboard** (optional) → Modern React admin interface
|
|
213
|
+
- 📡 **Real-time WebSockets** → Live updates with Centrifugo
|
|
214
|
+
- 🚀 **Production-ready** → Type-safe config, security hardened
|
|
247
215
|
|
|
248
216
|
<div align="center">
|
|
249
217
|
<img src="https://raw.githubusercontent.com/markolofsen/django-cfg/refs/heads/main/static/startup.png" alt="Django-CFG Startup Screen" width="800">
|
|
250
|
-
<p><em>Django-CFG startup
|
|
218
|
+
<p><em>Django-CFG startup with type-safe configuration validation</em></p>
|
|
251
219
|
</div>
|
|
252
220
|
|
|
253
221
|
**[📚 Installation Guide →](https://djangocfg.com/docs/getting-started/installation)**
|
|
254
222
|
|
|
255
223
|
---
|
|
256
224
|
|
|
257
|
-
###
|
|
225
|
+
### Try Live Demo
|
|
258
226
|
|
|
259
|
-
**See
|
|
227
|
+
**See Django-CFG in action:**
|
|
260
228
|
|
|
261
|
-
|
|
229
|
+
**[→ http://demo.djangocfg.com](http://demo.djangocfg.com)**
|
|
262
230
|
|
|
263
231
|
**Demo credentials:**
|
|
264
232
|
- **Admin:** `demo@djangocfg.com` / `demo2024`
|
|
265
233
|
- **User:** `user@djangocfg.com` / `user2024`
|
|
266
234
|
|
|
267
|
-
**
|
|
235
|
+
**Explore:** Modern admin • Next.js dashboard • AI agents • Real-time updates • Support system
|
|
268
236
|
|
|
269
237
|
---
|
|
270
238
|
|
|
271
239
|
## 💡 Core Features
|
|
272
240
|
|
|
273
|
-
### 🔒 Type-Safe
|
|
274
|
-
|
|
275
|
-
**Replace Django's settings.py with Pydantic v2 for complete type safety, IDE autocomplete, and startup validation.**
|
|
241
|
+
### 🔒 Type-Safe Configuration with Pydantic v2
|
|
276
242
|
|
|
277
|
-
|
|
243
|
+
**Replace error-prone settings.py with validated Pydantic models.**
|
|
278
244
|
|
|
245
|
+
#### Before: Django settings.py
|
|
279
246
|
```python
|
|
280
|
-
# settings.py -
|
|
247
|
+
# settings.py - Runtime errors, no validation
|
|
281
248
|
import os
|
|
282
249
|
|
|
283
250
|
DEBUG = os.getenv('DEBUG', 'False') == 'True' # ❌ String comparison bug
|
|
@@ -290,98 +257,164 @@ DATABASES = {
|
|
|
290
257
|
'PORT': DATABASE_PORT, # ❌ Type mismatch in production
|
|
291
258
|
}
|
|
292
259
|
}
|
|
293
|
-
# ... 200+ more lines
|
|
260
|
+
# ... 200+ more lines
|
|
294
261
|
```
|
|
295
262
|
|
|
296
|
-
#### After: Django-CFG
|
|
297
|
-
|
|
263
|
+
#### After: Django-CFG
|
|
298
264
|
```python
|
|
299
|
-
# config.py - Type-safe
|
|
300
|
-
from django_cfg import DjangoConfig
|
|
301
|
-
from django_cfg.models import DatabaseConfig
|
|
265
|
+
# config.py - Type-safe, validated at startup
|
|
266
|
+
from django_cfg import DjangoConfig, DatabaseConfig
|
|
302
267
|
|
|
303
268
|
class MyConfig(DjangoConfig):
|
|
304
|
-
"""Production-
|
|
269
|
+
"""Production-ready type-safe configuration"""
|
|
305
270
|
|
|
306
|
-
project_name: str = "My
|
|
307
|
-
debug: bool = False # ✅ Pydantic validates boolean
|
|
271
|
+
project_name: str = "My App"
|
|
272
|
+
debug: bool = False # ✅ Pydantic validates boolean
|
|
308
273
|
|
|
309
|
-
# Type-safe database
|
|
274
|
+
# Type-safe database with startup validation
|
|
310
275
|
databases: dict[str, DatabaseConfig] = {
|
|
311
276
|
"default": DatabaseConfig(
|
|
312
|
-
name="${DB_NAME}",
|
|
313
|
-
port=5432,
|
|
277
|
+
name="${DB_NAME}", # ✅ Validated at startup
|
|
278
|
+
port=5432, # ✅ Type-checked integer
|
|
314
279
|
)
|
|
315
280
|
}
|
|
316
281
|
```
|
|
317
282
|
|
|
318
|
-
**
|
|
319
|
-
- ✅ **Pydantic v2 validation** - catch
|
|
320
|
-
- ✅ **Full IDE autocomplete** - IntelliSense
|
|
321
|
-
- ✅ **90% less code** -
|
|
322
|
-
- ✅ **Type hints
|
|
283
|
+
**Benefits:**
|
|
284
|
+
- ✅ **Pydantic v2 validation** - catch errors before deployment
|
|
285
|
+
- ✅ **Full IDE autocomplete** - IntelliSense everywhere
|
|
286
|
+
- ✅ **90% less code** - 200+ lines → 30 lines
|
|
287
|
+
- ✅ **Type hints** - mypy and pyright compatible
|
|
288
|
+
|
|
289
|
+
**[📚 Type-safe configuration guide →](https://djangocfg.com/docs/fundamentals/core/type-safety)**
|
|
290
|
+
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
### ⚛️ Next.js Admin Integration
|
|
294
|
+
|
|
295
|
+
**Build modern admin interfaces with React** - the only Django framework with built-in Next.js integration.
|
|
296
|
+
|
|
297
|
+
```python
|
|
298
|
+
from django_cfg import DjangoConfig, NextJsAdminConfig
|
|
299
|
+
|
|
300
|
+
class MyConfig(DjangoConfig):
|
|
301
|
+
# One line for complete Next.js admin!
|
|
302
|
+
nextjs_admin: NextJsAdminConfig = NextJsAdminConfig(
|
|
303
|
+
project_path="../admin",
|
|
304
|
+
)
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
**What you get:**
|
|
308
|
+
- 🌐 **Three-in-One Architecture** - Public site + User dashboard + Admin panel in ONE Next.js project
|
|
309
|
+
- ⚙️ **Dual Admin Strategy** - Django Unfold (90% quick CRUD) + Next.js (10% complex features)
|
|
310
|
+
- ✨ **Zero Configuration** - Auto JWT auth, theme sync, TypeScript generation
|
|
311
|
+
- 📦 **60% Smaller** - ZIP deployment (~7MB vs ~20MB)
|
|
312
|
+
- ⚡ **Auto-Detection** - Dev mode automatically detected on ports 3000/3001
|
|
313
|
+
|
|
314
|
+
**No migration needed** - start with built-in admin, add Next.js when you need complex features!
|
|
323
315
|
|
|
324
|
-
**[📚
|
|
316
|
+
**[📚 Next.js Admin Documentation →](https://djangocfg.com/docs/features/integrations/nextjs-admin)**
|
|
325
317
|
|
|
326
318
|
---
|
|
327
319
|
|
|
328
|
-
###
|
|
320
|
+
### 📡 Real-Time WebSockets with Centrifugo
|
|
329
321
|
|
|
330
|
-
**
|
|
322
|
+
**Production-ready WebSocket integration** - live updates, notifications, and real-time collaboration.
|
|
323
|
+
|
|
324
|
+
```python
|
|
325
|
+
from django_cfg import DjangoConfig, CentrifugoConfig
|
|
326
|
+
|
|
327
|
+
class MyConfig(DjangoConfig):
|
|
328
|
+
# Enable real-time features
|
|
329
|
+
centrifugo: CentrifugoConfig = CentrifugoConfig(
|
|
330
|
+
enabled=True,
|
|
331
|
+
api_url="http://localhost:8001/api",
|
|
332
|
+
)
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
**Built-in features:**
|
|
336
|
+
- ⚡ **Live Updates** - Real-time data synchronization
|
|
337
|
+
- 🔔 **Notifications** - Push notifications to connected clients
|
|
338
|
+
- 👥 **Presence** - Track online users
|
|
339
|
+
- 💬 **Chat** - Real-time messaging out of the box
|
|
340
|
+
- 🔒 **JWT Auth** - Secure WebSocket connections
|
|
341
|
+
|
|
342
|
+
**[📚 Centrifugo Integration Guide →](https://djangocfg.com/docs/features/integrations/centrifugo)**
|
|
343
|
+
|
|
344
|
+
---
|
|
345
|
+
|
|
346
|
+
### 🤖 AI-Ready Infrastructure
|
|
347
|
+
|
|
348
|
+
**Built-in AI agent framework** - LLM workflow automation with Django ORM integration.
|
|
331
349
|
|
|
332
350
|
```python
|
|
333
351
|
from django_cfg import DjangoConfig
|
|
334
352
|
|
|
335
353
|
class MyConfig(DjangoConfig):
|
|
336
|
-
# AI
|
|
354
|
+
# AI features (optional)
|
|
337
355
|
openai_api_key: str = "${OPENAI_API_KEY}"
|
|
338
356
|
anthropic_api_key: str = "${ANTHROPIC_API_KEY}"
|
|
339
357
|
|
|
340
|
-
#
|
|
341
|
-
|
|
342
|
-
enable_knowbase: bool = True # Vector database + RAG
|
|
358
|
+
enable_agents: bool = True # AI workflow automation
|
|
359
|
+
enable_knowbase: bool = True # Vector DB + RAG
|
|
343
360
|
```
|
|
344
361
|
|
|
345
|
-
**
|
|
346
|
-
- 🤖 **AI Agents Framework** - Type-safe
|
|
347
|
-
- 📚 **Vector Database** - ChromaDB semantic search
|
|
348
|
-
- 🔍 **RAG
|
|
349
|
-
- 🎯 **Pydantic AI** -
|
|
350
|
-
- 🌐 **Multi-LLM** - OpenAI, Anthropic, Claude
|
|
362
|
+
**Features:**
|
|
363
|
+
- 🤖 **AI Agents Framework** - Type-safe LLM integration
|
|
364
|
+
- 📚 **Vector Database** - ChromaDB for semantic search
|
|
365
|
+
- 🔍 **RAG** - Retrieval-augmented generation
|
|
366
|
+
- 🎯 **Pydantic AI** - Validated AI input/output
|
|
367
|
+
- 🌐 **Multi-LLM** - OpenAI, Anthropic, Claude support
|
|
351
368
|
|
|
352
|
-
**[📚
|
|
369
|
+
**[📚 AI Agents Guide →](https://djangocfg.com/docs/ai-agents/introduction)**
|
|
353
370
|
|
|
354
371
|
---
|
|
355
372
|
|
|
356
|
-
### 📦 8
|
|
373
|
+
### 📦 8 Enterprise Apps Included
|
|
357
374
|
|
|
358
|
-
**Ship features in days, not months** -
|
|
375
|
+
**Ship features in days, not months** - production-ready apps out of the box:
|
|
359
376
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
377
|
+
| App | Description | Time Saved |
|
|
378
|
+
|-----|-------------|------------|
|
|
379
|
+
| 👤 **Accounts** | User management + OTP + SMS auth | 3-4 weeks |
|
|
380
|
+
| 🎫 **Support** | Ticketing system + SLA tracking | 2-3 weeks |
|
|
381
|
+
| 📧 **Newsletter** | Email campaigns + analytics | 2-3 weeks |
|
|
382
|
+
| 📊 **Leads** | CRM + sales pipeline | 2-3 weeks |
|
|
383
|
+
| 🤖 **AI Agents** | Workflow automation | 3-4 weeks |
|
|
384
|
+
| 📚 **KnowBase** | AI knowledge base + RAG | 2-3 weeks |
|
|
385
|
+
| 💳 **Payments** | Multi-provider payments | 2-3 weeks |
|
|
386
|
+
| 🔧 **Maintenance** | Multi-site management | 1-2 weeks |
|
|
368
387
|
|
|
369
|
-
**Total time saved: 18 months of development**
|
|
388
|
+
**Total time saved: 18+ months of development**
|
|
370
389
|
|
|
371
|
-
|
|
390
|
+
```python
|
|
391
|
+
class MyConfig(DjangoConfig):
|
|
392
|
+
# Enable apps as needed (one line each!)
|
|
393
|
+
enable_accounts: bool = True
|
|
394
|
+
enable_support: bool = True
|
|
395
|
+
enable_newsletter: bool = True
|
|
396
|
+
enable_leads: bool = True
|
|
397
|
+
enable_agents: bool = True
|
|
398
|
+
enable_knowbase: bool = True
|
|
399
|
+
enable_payments: bool = True
|
|
400
|
+
enable_maintenance: bool = True
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
**[📚 Built-in Apps Overview →](https://djangocfg.com/docs/features/built-in-apps/overview)**
|
|
372
404
|
|
|
373
405
|
---
|
|
374
406
|
|
|
375
407
|
### 🎨 Modern API UI with Tailwind 4
|
|
376
408
|
|
|
377
|
-
**Beautiful browsable API** - 88% smaller bundle,
|
|
409
|
+
**Beautiful browsable API** - 88% smaller bundle, modern design.
|
|
378
410
|
|
|
379
411
|
- ✅ Glass morphism design
|
|
380
412
|
- ✅ Light/Dark/Auto themes
|
|
381
413
|
- ✅ Command palette (⌘K)
|
|
382
|
-
- ✅ 88% smaller
|
|
414
|
+
- ✅ 88% smaller (278KB → 33KB)
|
|
415
|
+
- ✅ Auto-generated TypeScript clients
|
|
383
416
|
|
|
384
|
-
**[📚
|
|
417
|
+
**[📚 API Generation Guide →](https://djangocfg.com/docs/features/api-generation/overview)**
|
|
385
418
|
|
|
386
419
|
---
|
|
387
420
|
|
|
@@ -390,43 +423,58 @@ class MyConfig(DjangoConfig):
|
|
|
390
423
|
**Zero-config database routing** with automatic sharding:
|
|
391
424
|
|
|
392
425
|
```python
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
}
|
|
426
|
+
from django_cfg import DjangoConfig, DatabaseConfig
|
|
427
|
+
|
|
428
|
+
class MyConfig(DjangoConfig):
|
|
429
|
+
databases: dict[str, DatabaseConfig] = {
|
|
430
|
+
"default": DatabaseConfig(
|
|
431
|
+
name="${DB_NAME}",
|
|
432
|
+
),
|
|
433
|
+
"analytics": DatabaseConfig(
|
|
434
|
+
name="${ANALYTICS_DB}",
|
|
435
|
+
routing_apps=["analytics", "reports"], # Auto-route!
|
|
436
|
+
),
|
|
437
|
+
}
|
|
399
438
|
```
|
|
400
439
|
|
|
401
440
|
✅ Auto-routes read/write • ✅ Cross-DB transactions • ✅ Connection pooling
|
|
402
441
|
|
|
403
|
-
**[📚 Multi-
|
|
442
|
+
**[📚 Multi-Database Guide →](https://djangocfg.com/docs/fundamentals/database/multi-database)**
|
|
404
443
|
|
|
405
444
|
---
|
|
406
445
|
|
|
407
446
|
## ⚙️ Complete Configuration Example
|
|
408
447
|
|
|
409
|
-
**All
|
|
448
|
+
**All features in one DjangoConfig:**
|
|
410
449
|
|
|
411
450
|
```python
|
|
412
|
-
from django_cfg import DjangoConfig
|
|
413
|
-
from django_cfg.models import DatabaseConfig, CacheConfig
|
|
451
|
+
from django_cfg import DjangoConfig, DatabaseConfig, CacheConfig, NextJsAdminConfig
|
|
414
452
|
|
|
415
453
|
class ProductionConfig(DjangoConfig):
|
|
416
|
-
# Project
|
|
454
|
+
# Project
|
|
417
455
|
project_name: str = "My Enterprise App"
|
|
418
456
|
secret_key: str = "${SECRET_KEY}"
|
|
419
457
|
debug: bool = False
|
|
420
458
|
|
|
421
|
-
#
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
459
|
+
# Next.js Admin (optional)
|
|
460
|
+
nextjs_admin: NextJsAdminConfig = NextJsAdminConfig(
|
|
461
|
+
project_path="../admin",
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
# Real-time WebSockets (optional)
|
|
465
|
+
centrifugo: CentrifugoConfig = CentrifugoConfig(
|
|
466
|
+
enabled=True,
|
|
467
|
+
)
|
|
468
|
+
|
|
469
|
+
# 8 Enterprise Apps (enable as needed)
|
|
470
|
+
enable_accounts: bool = True # User management
|
|
471
|
+
enable_support: bool = True # Ticketing
|
|
472
|
+
enable_newsletter: bool = True # Email campaigns
|
|
473
|
+
enable_leads: bool = True # CRM
|
|
474
|
+
enable_agents: bool = True # AI automation
|
|
475
|
+
enable_knowbase: bool = True # Vector DB
|
|
476
|
+
enable_payments: bool = True # Payments
|
|
477
|
+
enable_maintenance: bool = True # Site management
|
|
430
478
|
|
|
431
479
|
# Infrastructure
|
|
432
480
|
databases: dict[str, DatabaseConfig] = {
|
|
@@ -440,75 +488,84 @@ class ProductionConfig(DjangoConfig):
|
|
|
440
488
|
openai_api_key: str = "${OPENAI_API_KEY}"
|
|
441
489
|
anthropic_api_key: str = "${ANTHROPIC_API_KEY}"
|
|
442
490
|
|
|
443
|
-
#
|
|
444
|
-
twilio_account_sid: str = "${TWILIO_ACCOUNT_SID}"
|
|
445
|
-
stripe_api_key: str = "${STRIPE_API_KEY}"
|
|
446
|
-
cloudflare_api_token: str = "${CF_API_TOKEN}"
|
|
491
|
+
# Integrations
|
|
492
|
+
twilio_account_sid: str = "${TWILIO_ACCOUNT_SID}"
|
|
493
|
+
stripe_api_key: str = "${STRIPE_API_KEY}"
|
|
494
|
+
cloudflare_api_token: str = "${CF_API_TOKEN}"
|
|
447
495
|
```
|
|
448
496
|
|
|
449
|
-
**[📚
|
|
497
|
+
**[📚 Configuration Reference →](https://djangocfg.com/docs/getting-started/configuration)**
|
|
450
498
|
|
|
451
499
|
---
|
|
452
500
|
|
|
453
|
-
## 📊
|
|
501
|
+
## 📊 Comparison with Alternatives
|
|
454
502
|
|
|
455
|
-
|
|
503
|
+
### Django-CFG vs Traditional Solutions
|
|
456
504
|
|
|
457
|
-
| Feature |
|
|
458
|
-
|
|
459
|
-
| **Type
|
|
460
|
-
| **IDE Autocomplete** | ❌ None | ❌ None |
|
|
461
|
-
| **Startup Validation** | ❌ No | ⚠️ Partial |
|
|
462
|
-
| **
|
|
463
|
-
| **
|
|
464
|
-
| **
|
|
465
|
-
| **AI
|
|
466
|
-
| **Setup Time** | 🟡 Weeks | 🟡 Hours | 🟡
|
|
467
|
-
| **
|
|
468
|
-
| **Configuration Lines** | ⚠️ 200+ | ⚠️ 150+ | ⚠️ 200+ | ⚠️ 100+ | ✅ **30 lines** |
|
|
505
|
+
| Feature | settings.py | django-environ | pydantic-settings | **Django-CFG** |
|
|
506
|
+
|---------|-------------|----------------|-------------------|----------------|
|
|
507
|
+
| **Type Safety** | ❌ Runtime | ⚠️ Basic | ✅ Pydantic | ✅ **Full Pydantic v2** |
|
|
508
|
+
| **IDE Autocomplete** | ❌ None | ❌ None | ⚠️ Partial | ✅ **100%** |
|
|
509
|
+
| **Startup Validation** | ❌ No | ⚠️ Partial | ✅ Yes | ✅ **Yes + Custom** |
|
|
510
|
+
| **Next.js Admin** | ❌ Manual | ❌ None | ❌ None | ✅ **Built-in** |
|
|
511
|
+
| **WebSocket (Centrifugo)** | ❌ Manual | ❌ None | ❌ None | ✅ **Built-in** |
|
|
512
|
+
| **Enterprise Apps** | ❌ Build all | ❌ None | ❌ None | ✅ **8 included** |
|
|
513
|
+
| **AI Framework** | ❌ Manual | ❌ None | ❌ None | ✅ **Built-in** |
|
|
514
|
+
| **Setup Time** | 🟡 Weeks | 🟡 Hours | 🟡 Days | ✅ **30 seconds** |
|
|
515
|
+
| **Config Lines** | ⚠️ 200+ | ⚠️ 150+ | ⚠️ 100+ | ✅ **30 lines** |
|
|
469
516
|
|
|
470
517
|
**Legend:** ✅ Excellent | 🟡 Requires Work | ⚠️ Partial | ❌ Not Available
|
|
471
518
|
|
|
472
|
-
**[📚
|
|
519
|
+
**[📚 Detailed Comparison Guide →](https://djangocfg.com/docs/getting-started/django-cfg-vs-alternatives)**
|
|
473
520
|
|
|
474
521
|
---
|
|
475
522
|
|
|
476
523
|
## 📚 Documentation
|
|
477
524
|
|
|
478
525
|
### 🚀 Getting Started
|
|
479
|
-
- **[Installation](https://djangocfg.com/docs/getting-started/installation)** - Quick setup
|
|
526
|
+
- **[Installation](https://djangocfg.com/docs/getting-started/installation)** - Quick setup
|
|
480
527
|
- **[First Project](https://djangocfg.com/docs/getting-started/first-project)** - Create your first app
|
|
481
|
-
- **[Configuration](https://djangocfg.com/docs/getting-started/configuration)** - Type-safe config
|
|
528
|
+
- **[Configuration](https://djangocfg.com/docs/getting-started/configuration)** - Type-safe config
|
|
482
529
|
- **[Why Django-CFG?](https://djangocfg.com/docs/getting-started/why-django-cfg)** - Full comparison
|
|
483
530
|
|
|
531
|
+
### ⚛️ Next.js Integration
|
|
532
|
+
- **[Overview](https://djangocfg.com/docs/features/integrations/nextjs-admin)** - Three-in-One architecture
|
|
533
|
+
- **[Core Concepts](https://djangocfg.com/docs/features/integrations/nextjs-admin/concepts)** - Philosophy & design
|
|
534
|
+
- **[Quick Start](https://djangocfg.com/docs/features/integrations/nextjs-admin/quick-start)** - 5-minute setup
|
|
535
|
+
- **[Configuration](https://djangocfg.com/docs/features/integrations/nextjs-admin/configuration)** - All options
|
|
536
|
+
|
|
537
|
+
### 📡 Real-Time Features
|
|
538
|
+
- **[Centrifugo Integration](https://djangocfg.com/docs/features/integrations/centrifugo)** - WebSocket setup
|
|
539
|
+
- **[Live Updates](https://djangocfg.com/docs/features/integrations/centrifugo/live-updates)** - Real-time data
|
|
540
|
+
|
|
484
541
|
### 🏗️ Core Features
|
|
485
|
-
- **[Built-in Apps](https://djangocfg.com/docs/features/built-in-apps)** - 8 enterprise apps
|
|
486
|
-
- **[API Generation](https://djangocfg.com/docs/features/api-generation)** - Auto
|
|
487
|
-
- **[Database](https://djangocfg.com/docs/fundamentals/database)** - Multi-DB routing
|
|
488
|
-
- **[
|
|
542
|
+
- **[Built-in Apps](https://djangocfg.com/docs/features/built-in-apps/overview)** - 8 enterprise apps
|
|
543
|
+
- **[API Generation](https://djangocfg.com/docs/features/api-generation/overview)** - Auto TypeScript clients
|
|
544
|
+
- **[Database](https://djangocfg.com/docs/fundamentals/database/multi-database)** - Multi-DB routing
|
|
545
|
+
- **[Type Safety](https://djangocfg.com/docs/fundamentals/core/type-safety)** - Pydantic validation
|
|
489
546
|
|
|
490
|
-
### 🤖 AI
|
|
547
|
+
### 🤖 AI Features (Optional)
|
|
491
548
|
- **[AI Agents](https://djangocfg.com/docs/ai-agents/introduction)** - Workflow automation
|
|
492
549
|
- **[Creating Agents](https://djangocfg.com/docs/ai-agents/creating-agents)** - Build custom agents
|
|
493
|
-
- **[Django Integration](https://djangocfg.com/docs/ai-agents/django-integration)** -
|
|
550
|
+
- **[Django Integration](https://djangocfg.com/docs/ai-agents/django-integration)** - ORM integration
|
|
494
551
|
|
|
495
552
|
### 🚀 Deployment
|
|
496
|
-
- **[Production Config](https://djangocfg.com/docs/deployment)** -
|
|
497
|
-
- **[CLI Commands](https://djangocfg.com/docs/cli)** - 50+
|
|
553
|
+
- **[Production Config](https://djangocfg.com/docs/deployment)** - Best practices
|
|
554
|
+
- **[CLI Commands](https://djangocfg.com/docs/cli)** - 50+ commands
|
|
498
555
|
|
|
499
556
|
---
|
|
500
557
|
|
|
501
558
|
## 🤝 Community & Support
|
|
502
559
|
|
|
503
560
|
### Resources
|
|
504
|
-
- 🌐 **[djangocfg.com](https://djangocfg.com/)** - Official website &
|
|
561
|
+
- 🌐 **[djangocfg.com](https://djangocfg.com/)** - Official website & docs
|
|
505
562
|
- 🐙 **[GitHub](https://github.com/markolofsen/django-cfg)** - Source code & issues
|
|
506
563
|
- 💬 **[Discussions](https://github.com/markolofsen/django-cfg/discussions)** - Community support
|
|
507
564
|
|
|
508
565
|
### Links
|
|
509
|
-
- **[🚀 AI Project Generator](https://editor.djangocfg.com)** - Generate projects with AI
|
|
510
566
|
- **[🎯 Live Demo](http://demo.djangocfg.com)** - See it in action
|
|
511
567
|
- **[📦 PyPI](https://pypi.org/project/django-cfg/)** - Package repository
|
|
568
|
+
- **[📚 Documentation](https://djangocfg.com/docs)** - Complete guides
|
|
512
569
|
|
|
513
570
|
---
|
|
514
571
|
|
|
@@ -524,12 +581,12 @@ class ProductionConfig(DjangoConfig):
|
|
|
524
581
|
|
|
525
582
|
<div align="center">
|
|
526
583
|
|
|
527
|
-
**Django
|
|
584
|
+
**Modern Django Framework** • **Type-Safe Configuration** • **Next.js Admin** • **Real-Time WebSockets** • **AI-Ready**
|
|
528
585
|
|
|
529
|
-
Django-CFG is the
|
|
586
|
+
Django-CFG is the modern Django framework for enterprise applications. Built with Pydantic v2 for type-safe configuration, includes Next.js admin integration, Centrifugo WebSocket support, AI agent framework, and 8 production-ready apps. Perfect for building scalable Django applications with reduced boilerplate and enterprise features out of the box.
|
|
530
587
|
|
|
531
588
|
---
|
|
532
589
|
|
|
533
|
-
**Get Started:** **[Documentation](https://djangocfg.com/docs/getting-started/intro)** • **[
|
|
590
|
+
**Get Started:** **[Documentation](https://djangocfg.com/docs/getting-started/intro)** • **[Live Demo](http://demo.djangocfg.com)** • **[GitHub](https://github.com/markolofsen/django-cfg)**
|
|
534
591
|
|
|
535
592
|
</div>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
django_cfg/README.md,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
django_cfg/__init__.py,sha256=
|
|
2
|
+
django_cfg/__init__.py,sha256=ixk70FXf2taCPetppHWPXJltlOmnzJ1Cql1usdNzh2g,1620
|
|
3
3
|
django_cfg/apps.py,sha256=72m3uuvyqGiLx6gOfE-BD3P61jddCCERuBOYpxTX518,1605
|
|
4
4
|
django_cfg/config.py,sha256=y4Z3rnYsHBE0TehpwAIPaxr---mkvyKrZGGsNwYso74,1398
|
|
5
5
|
django_cfg/apps/__init__.py,sha256=JtDmEYt1OcleWM2ZaeX0LKDnRQzPOavfaXBWG4ECB5Q,26
|
|
@@ -228,7 +228,7 @@ django_cfg/apps/frontend/apps.py,sha256=9eEK0Tq2nOsol7xSK5aobdwTDJTJrWx6Iy1I1DQb
|
|
|
228
228
|
django_cfg/apps/frontend/setup.py,sha256=TxKQwQw4xTF6VSyhrQBzbUsdsVQR9JHdjc36LZKeQh4,2444
|
|
229
229
|
django_cfg/apps/frontend/test_routing.py,sha256=fshJOR9ln7m3gXY9EI1_ix_6E5xua6DR264b16RIF-w,4832
|
|
230
230
|
django_cfg/apps/frontend/urls.py,sha256=Vz22_2i2w1J0KQYDCxHnTF5rUf32kUUSBDJZrP07XgY,284
|
|
231
|
-
django_cfg/apps/frontend/views.py,sha256=
|
|
231
|
+
django_cfg/apps/frontend/views.py,sha256=u4B7YNwaAupu1yoxYLR8JNVAOmmtgYjHEmSpI3J53LQ,13937
|
|
232
232
|
django_cfg/apps/frontend/templates/frontend/404.html,sha256=LCFig_dcgDDmYKhgOLu8R2KDs_aQS6Es6rAxLTAEXWs,2175
|
|
233
233
|
django_cfg/apps/knowbase/README.md,sha256=HXt_J6WCN-LsMhA7p9mdvih07_vp_r_hkPdmqHhNEeo,3965
|
|
234
234
|
django_cfg/apps/knowbase/__init__.py,sha256=cfGnxDQwjajPhUoleKkgvdabJcB0LdXEglnsBojKkPo,1045
|
|
@@ -1021,7 +1021,7 @@ django_cfg/modules/django_unfold/models/navigation.py,sha256=PPEeqA2HBaA1-VjADiX
|
|
|
1021
1021
|
django_cfg/modules/nextjs_admin/__init__.py,sha256=lfrZYyNRExH3Z5De8G4hQBIZoFlW5Ejze3couNrztbY,312
|
|
1022
1022
|
django_cfg/modules/nextjs_admin/apps.py,sha256=HxVUMmWTKdYpwJ00iIfWVFsBzsawsOVhEPZqjk_izjI,347
|
|
1023
1023
|
django_cfg/modules/nextjs_admin/urls.py,sha256=7n0yStm0WNchw14Rtu_mgsIA3WKQsYP9WZt3-YOUWjU,603
|
|
1024
|
-
django_cfg/modules/nextjs_admin/views.py,sha256=
|
|
1024
|
+
django_cfg/modules/nextjs_admin/views.py,sha256=kOyfb3eQoDB68ojoxhf4KgZEcSkSW8n5xSkceKQHcMg,9405
|
|
1025
1025
|
django_cfg/modules/nextjs_admin/models/__init__.py,sha256=WGw9KXcYd1O9AoA_bpMoz2gLZUlRzjGmUBjjbObcUi0,100
|
|
1026
1026
|
django_cfg/modules/nextjs_admin/models/config.py,sha256=0ADqLuiywSCQfx_z9dkwjFCca3lr3F2uQffIjTr_QXw,5864
|
|
1027
1027
|
django_cfg/modules/nextjs_admin/templatetags/__init__.py,sha256=ChVBnJggCIY8rMhfyJFoA8k0qKo-8FtJknrk54Vx4wM,51
|
|
@@ -1051,7 +1051,7 @@ django_cfg/static/admin/js/alpine/dashboard-tabs.js,sha256=ob8Q_I9lFLDv_hFERXgTy
|
|
|
1051
1051
|
django_cfg/static/admin/js/alpine/system-metrics.js,sha256=m-Fg55K_vpHXToD46PXL9twl4OBF_V9MONvbSWbQqDw,440
|
|
1052
1052
|
django_cfg/static/admin/js/alpine/toggle-section.js,sha256=T141NFmy0fRJyGGuuaCJRjJXwPam-xxtQNW1hi8BJbc,672
|
|
1053
1053
|
django_cfg/static/frontend/admin.zip,sha256=n2G8ajruLqojcjqvw2pMEPjgpSTTwcmJxkeIQxJVw9U,7626768
|
|
1054
|
-
django_cfg/static/frontend/nextjs_admin.zip,sha256=
|
|
1054
|
+
django_cfg/static/frontend/nextjs_admin.zip,sha256=ezN08XaFPMMkQMGh2MWWif1QZkhStiBy1azw37KxKWw,7548354
|
|
1055
1055
|
django_cfg/static/js/api-loader.mjs,sha256=boGqqRGnFR-Mzo_RQOjhAzNvsb7QxZddSwMKROzkk9Q,5163
|
|
1056
1056
|
django_cfg/static/js/api/base.mjs,sha256=KUxZHHdELAV8mNnACpwJRvaQhdJxp-n5LFEQ4oUZxBo,4707
|
|
1057
1057
|
django_cfg/static/js/api/index.mjs,sha256=_-Q04jjHcgwi4CGfiaLyiOR6NW7Yu1HBhJWp2J1cjpc,2538
|
|
@@ -1073,11 +1073,11 @@ django_cfg/static/js/api/support/index.mjs,sha256=oPA3iGkUWYyKQuJlI5-tSxD3AOhwlA
|
|
|
1073
1073
|
django_cfg/static/js/api/tasks/client.mjs,sha256=tIy8K-finXzTUL9kOo_L4Q1kchDaHyuzjwS4VymiWPM,3579
|
|
1074
1074
|
django_cfg/static/js/api/tasks/index.mjs,sha256=yCY1GzdD-RtFZ3pAfk1l0msgO1epyo0lsGCjH0g1Afc,294
|
|
1075
1075
|
django_cfg/templates/__init__.py,sha256=IzLjt-a7VIJ0OutmAE1_-w0_LpL2u0MgGpnIabjZuW8,19
|
|
1076
|
-
django_cfg/templates/admin/index.html,sha256=
|
|
1076
|
+
django_cfg/templates/admin/index.html,sha256=RidRvZwc6LFzRi8l6vHBgyM_CD0yvhPWvr40uVKCClY,18138
|
|
1077
1077
|
django_cfg/templates/emails/base_email.html,sha256=TWcvYa2IHShlF_E8jf1bWZStRO0v8G4L_GexPxvz6XQ,8836
|
|
1078
1078
|
django_cfg/templates/unfold/layouts/skeleton.html,sha256=2ArkcNZ34mFs30cOAsTQ1EZiDXcB0aVxkO71lJq9SLE,718
|
|
1079
1079
|
django_cfg/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
1080
|
-
django_cfg/templatetags/django_cfg.py,sha256=
|
|
1080
|
+
django_cfg/templatetags/django_cfg.py,sha256=0m7YadW6ifWuUEA9qe9IZ6zY4wt4F4ke0RXo3ngA5p8,9334
|
|
1081
1081
|
django_cfg/utils/__init__.py,sha256=64wwXJuXytvwt8Ze_erSR2HmV07nGWJ6DV5wloRBvYE,435
|
|
1082
1082
|
django_cfg/utils/path_resolution.py,sha256=2n0I04lQkSssFaELu3A93YyMAl1K10KPdpxMt5k4Iy0,13341
|
|
1083
1083
|
django_cfg/utils/smart_defaults.py,sha256=ZUj6K_Deq-fp5O0Dy_Emt257UWFn0f9bkgFv9YCR58U,9239
|
|
@@ -1085,9 +1085,9 @@ django_cfg/utils/version_check.py,sha256=WO51J2m2e-wVqWCRwbultEwu3q1lQasV67Mw2aa
|
|
|
1085
1085
|
django_cfg/CHANGELOG.md,sha256=jtT3EprqEJkqSUh7IraP73vQ8PmKUMdRtznQsEnqDZk,2052
|
|
1086
1086
|
django_cfg/CONTRIBUTING.md,sha256=DU2kyQ6PU0Z24ob7O_OqKWEYHcZmJDgzw-lQCmu6uBg,3041
|
|
1087
1087
|
django_cfg/LICENSE,sha256=xHuytiUkSZCRG3N11nk1X6q1_EGQtv6aL5O9cqNRhKE,1071
|
|
1088
|
-
django_cfg/pyproject.toml,sha256=
|
|
1089
|
-
django_cfg-1.4.
|
|
1090
|
-
django_cfg-1.4.
|
|
1091
|
-
django_cfg-1.4.
|
|
1092
|
-
django_cfg-1.4.
|
|
1093
|
-
django_cfg-1.4.
|
|
1088
|
+
django_cfg/pyproject.toml,sha256=ca2iRiN-r6x_tF37fS5CdEMhQBO_Te6O9IA1IV7Du3U,8572
|
|
1089
|
+
django_cfg-1.4.93.dist-info/METADATA,sha256=f0X6l8mcxPU4yKPF7Jyql1DvdN1m_Wj3BvJJkheKCj4,23733
|
|
1090
|
+
django_cfg-1.4.93.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
1091
|
+
django_cfg-1.4.93.dist-info/entry_points.txt,sha256=Ucmde4Z2wEzgb4AggxxZ0zaYDb9HpyE5blM3uJ0_VNg,56
|
|
1092
|
+
django_cfg-1.4.93.dist-info/licenses/LICENSE,sha256=xHuytiUkSZCRG3N11nk1X6q1_EGQtv6aL5O9cqNRhKE,1071
|
|
1093
|
+
django_cfg-1.4.93.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|