django-cfg 1.4.84__py3-none-any.whl → 1.4.85__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.

Files changed (86) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/dashboard/serializers/__init__.py +55 -0
  3. django_cfg/apps/dashboard/serializers/activity.py +38 -0
  4. django_cfg/apps/dashboard/serializers/apizones.py +26 -0
  5. django_cfg/apps/dashboard/serializers/base.py +16 -0
  6. django_cfg/apps/dashboard/serializers/charts.py +44 -0
  7. django_cfg/apps/dashboard/serializers/commands.py +26 -0
  8. django_cfg/apps/dashboard/serializers/overview.py +34 -0
  9. django_cfg/apps/dashboard/serializers/statistics.py +46 -0
  10. django_cfg/apps/dashboard/serializers/system.py +58 -0
  11. django_cfg/apps/dashboard/services/__init__.py +10 -1
  12. django_cfg/apps/dashboard/services/apizones_service.py +119 -0
  13. django_cfg/apps/dashboard/services/charts_service.py +266 -0
  14. django_cfg/apps/dashboard/services/commands_service.py +142 -0
  15. django_cfg/apps/dashboard/services/statistics_service.py +262 -104
  16. django_cfg/apps/dashboard/urls.py +25 -6
  17. django_cfg/apps/dashboard/views/__init__.py +23 -0
  18. django_cfg/apps/dashboard/views/activity_views.py +83 -0
  19. django_cfg/apps/dashboard/views/apizones_views.py +73 -0
  20. django_cfg/apps/dashboard/views/charts_views.py +159 -0
  21. django_cfg/apps/dashboard/views/commands_views.py +73 -0
  22. django_cfg/apps/dashboard/views/overview_views.py +92 -0
  23. django_cfg/apps/dashboard/views/statistics_views.py +105 -0
  24. django_cfg/apps/dashboard/views/system_views.py +73 -0
  25. django_cfg/modules/django_unfold/callbacks/main.py +7 -6
  26. django_cfg/modules/django_unfold/dashboard.py +1 -36
  27. django_cfg/modules/django_unfold/models/config.py +102 -73
  28. django_cfg/modules/django_unfold/tailwind.py +31 -79
  29. django_cfg/pyproject.toml +1 -1
  30. django_cfg/static/frontend/admin/404.html +1 -1
  31. django_cfg/static/frontend/admin/500.html +1 -1
  32. django_cfg/static/frontend/admin/_next/static/BembwiEtlu4eFl3OX7n1k/_buildManifest.js +1 -0
  33. django_cfg/static/frontend/admin/_next/static/chunks/23004-faae121bbfecc163.js +1 -0
  34. django_cfg/static/frontend/admin/_next/static/chunks/{20695.a7d37b6c40ad3f58.js → 43076.55dd23b6cd68edb0.js} +1 -1
  35. django_cfg/static/frontend/admin/_next/static/chunks/50314-3b9d15242191c8bc.js +1 -0
  36. django_cfg/static/frontend/admin/_next/static/chunks/{64330.2ef79bccd7d4e363.js → 64330.41858e98c0e5173b.js} +1 -1
  37. django_cfg/static/frontend/admin/_next/static/chunks/6766.8d01e44e83070e83.js +1 -0
  38. django_cfg/static/frontend/admin/_next/static/chunks/{96168.eb7fdb721b9cdb00.js → 96168.b7197f890097df6e.js} +1 -1
  39. django_cfg/static/frontend/admin/_next/static/chunks/pages/{404-c283223d1afd02a2.js → 404-cf71cd7b3cb005e5.js} +1 -1
  40. django_cfg/static/frontend/admin/_next/static/chunks/pages/{500-389d6d3e1f2f7fda.js → 500-ff19c7842e3df415.js} +1 -1
  41. django_cfg/static/frontend/admin/_next/static/chunks/pages/_app-f62e5528fbcbb6b3.js +272 -0
  42. django_cfg/static/frontend/admin/_next/static/chunks/pages/{_error-5291033275c26d09.js → _error-87f3fdc2aa131e77.js} +1 -1
  43. django_cfg/static/frontend/admin/_next/static/chunks/pages/{index-d7bc30185f52cbca.js → index-69f737d4802cc5b7.js} +1 -1
  44. django_cfg/static/frontend/admin/_next/static/chunks/pages/private/centrifugo-f24beb6ed3955aa8.js +1 -0
  45. django_cfg/static/frontend/admin/_next/static/chunks/pages/private/{profile-e93a65e8e7d9022b.js → profile-b8045f993287f1a7.js} +1 -1
  46. django_cfg/static/frontend/admin/_next/static/chunks/pages/private/{ui-669e8f2a785beba2.js → ui-373fff8b42878e64.js} +1 -1
  47. django_cfg/static/frontend/admin/_next/static/chunks/pages/private-fe9faa86ecdb0ce6.js +1 -0
  48. django_cfg/static/frontend/admin/_next/static/chunks/{webpack-92add5f95c66e349.js → webpack-7c456a65e96eb97e.js} +1 -1
  49. django_cfg/static/frontend/admin/_next/static/css/5f9a37b6e6a72303.css +3 -0
  50. django_cfg/static/frontend/admin/auth.html +1 -1
  51. django_cfg/static/frontend/admin/index.html +1 -1
  52. django_cfg/static/frontend/admin/legal/cookies.html +1 -1
  53. django_cfg/static/frontend/admin/legal/privacy.html +1 -1
  54. django_cfg/static/frontend/admin/legal/security.html +1 -1
  55. django_cfg/static/frontend/admin/legal/terms.html +1 -1
  56. django_cfg/static/frontend/admin/private/centrifugo.html +1 -1
  57. django_cfg/static/frontend/admin/private/profile.html +1 -1
  58. django_cfg/static/frontend/admin/private/ui.html +1 -1
  59. django_cfg/static/frontend/admin/private.html +1 -1
  60. django_cfg/templates/admin/index.html +237 -5
  61. django_cfg/templates/admin/sections/commands_section.html +5 -0
  62. django_cfg/templates/admin/sections/documentation_section.html +5 -0
  63. django_cfg/templates/admin/sections/overview_section.html +5 -0
  64. django_cfg/templates/admin/sections/stats_section.html +5 -0
  65. django_cfg/templates/admin/sections/system_section.html +5 -0
  66. django_cfg/templates/admin/sections/widgets_section.html +11 -0
  67. django_cfg/templates/unfold/layouts/skeleton.html +27 -0
  68. django_cfg/templatetags/django_cfg.py +53 -0
  69. {django_cfg-1.4.84.dist-info → django_cfg-1.4.85.dist-info}/METADATA +1 -1
  70. {django_cfg-1.4.84.dist-info → django_cfg-1.4.85.dist-info}/RECORD +74 -51
  71. django_cfg/apps/dashboard/api/__init__.py +0 -27
  72. django_cfg/apps/dashboard/api/serializers.py +0 -165
  73. django_cfg/apps/dashboard/api/viewsets.py +0 -257
  74. django_cfg/static/frontend/admin/_next/static/-Zk0eDB7OJOEFrFyR5BwZ/_buildManifest.js +0 -1
  75. django_cfg/static/frontend/admin/_next/static/chunks/43076-4be6a9794e9c3e8b.js +0 -1
  76. django_cfg/static/frontend/admin/_next/static/chunks/50314-79c02212788f1ec7.js +0 -1
  77. django_cfg/static/frontend/admin/_next/static/chunks/6766.d62fed7cd4761148.js +0 -1
  78. django_cfg/static/frontend/admin/_next/static/chunks/82296-a2c8d38f62224be5.js +0 -1
  79. django_cfg/static/frontend/admin/_next/static/chunks/pages/_app-f25bec36bbdc9625.js +0 -272
  80. django_cfg/static/frontend/admin/_next/static/chunks/pages/private/centrifugo-22532c65971225eb.js +0 -1
  81. django_cfg/static/frontend/admin/_next/static/chunks/pages/private-a8a9ba76f2c75354.js +0 -1
  82. django_cfg/static/frontend/admin/_next/static/css/78d677ac1677c210.css +0 -3
  83. /django_cfg/static/frontend/admin/_next/static/{-Zk0eDB7OJOEFrFyR5BwZ → BembwiEtlu4eFl3OX7n1k}/_ssgManifest.js +0 -0
  84. {django_cfg-1.4.84.dist-info → django_cfg-1.4.85.dist-info}/WHEEL +0 -0
  85. {django_cfg-1.4.84.dist-info → django_cfg-1.4.85.dist-info}/entry_points.txt +0 -0
  86. {django_cfg-1.4.84.dist-info → django_cfg-1.4.85.dist-info}/licenses/LICENSE +0 -0
@@ -2,6 +2,7 @@
2
2
 
3
3
  {% load static %}
4
4
  {% load unfold %}
5
+ {% load django_cfg %}
5
6
 
6
7
  {% block extrahead %}
7
8
  {{ block.super }}
@@ -10,14 +11,23 @@
10
11
  <style>
11
12
  #nextjs-dashboard-iframe {
12
13
  width: 100%;
13
- height: calc(100vh - 120px);
14
+ min-height: 600px;
15
+ height: auto;
14
16
  border: none;
15
17
  display: block;
18
+ visibility: hidden !important;
19
+ opacity: 0 !important;
20
+ transition: height 0.2s ease-in-out, opacity 0.3s ease-in-out;
21
+ }
22
+
23
+ #nextjs-dashboard-iframe.loaded {
24
+ visibility: visible !important;
25
+ opacity: 1 !important;
16
26
  }
17
27
 
18
28
  .iframe-container {
19
29
  position: relative;
20
- background: #f9fafb;
30
+ background: transparent;
21
31
  }
22
32
 
23
33
  .iframe-loading {
@@ -63,16 +73,31 @@
63
73
  {% block content %}
64
74
  <!-- Main Container -->
65
75
  {% component "unfold/components/container.html" %}
76
+ {% if is_frontend_dev_mode %}
77
+ <!-- Development Mode Badge -->
78
+ <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">
79
+ <div class="flex items-center">
80
+ <svg class="w-5 h-5 mr-2" fill="currentColor" viewBox="0 0 20 20">
81
+ <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"/>
82
+ </svg>
83
+ <div>
84
+ <p class="font-bold">Frontend Development Mode Active</p>
85
+ <p class="text-sm">Loading Next.js from <code class="bg-yellow-200 dark:bg-yellow-800 px-1 rounded">{% nextjs_admin_url %}</code></p>
86
+ </div>
87
+ </div>
88
+ </div>
89
+ {% endif %}
90
+
66
91
  <!-- Next.js Dashboard iframe -->
67
92
  <div class="iframe-container">
68
93
  <div class="iframe-loading" id="iframe-loading">
69
94
  <div class="spinner"></div>
70
- <p>Loading Next.js Dashboard...</p>
95
+ <p id="loading-text">Loading Next.js Dashboard...</p>
71
96
  </div>
72
97
 
73
98
  <iframe
74
99
  id="nextjs-dashboard-iframe"
75
- src="/cfg/admin/private/"
100
+ src="{% nextjs_admin_url 'private' %}"
76
101
  title="Next.js Dashboard"
77
102
  sandbox="allow-same-origin allow-scripts allow-forms allow-popups allow-modals"
78
103
  ></iframe>
@@ -100,15 +125,222 @@
100
125
  (function() {
101
126
  const iframe = document.getElementById('nextjs-dashboard-iframe');
102
127
  const loading = document.getElementById('iframe-loading');
128
+ const loadingText = document.getElementById('loading-text');
129
+
130
+ // Log initial state
131
+ console.log('[Parent] 🎬 Initial iframe state:', {
132
+ visibility: window.getComputedStyle(iframe).visibility,
133
+ opacity: window.getComputedStyle(iframe).opacity,
134
+ hasLoadedClass: iframe.classList.contains('loaded')
135
+ });
136
+
137
+ // Detect iframe origin based on src
138
+ const iframeUrl = new URL(iframe.src);
139
+ const iframeOrigin = iframeUrl.origin;
140
+ const isDevMode = iframeOrigin !== window.location.origin;
141
+
142
+ // Debug logging (uncomment for debugging)
143
+ // console.log('[Parent] 🚀 Initializing iframe communication');
144
+ // console.log('[Parent] 📍 Parent origin:', window.location.origin);
145
+ // console.log('[Parent] 📍 iframe origin:', iframeOrigin);
146
+ // console.log('[Parent] 🔧 Dev mode:', isDevMode);
103
147
 
104
148
  iframe.addEventListener('load', function() {
105
- loading.classList.add('hidden');
149
+ // console.log('[Parent] 🎬 iframe load event fired');
150
+
151
+ // Send initial data (theme + auth) to iframe after a small delay
152
+ // to ensure iframe is fully initialized
153
+ setTimeout(() => {
154
+ // console.log('[Parent] ⏱️ Sending initial data after iframe load');
155
+ sendDataToIframe();
156
+ }, 100);
106
157
  });
107
158
 
108
159
  // Hide loading after 5 seconds if iframe doesn't load
109
160
  setTimeout(() => {
110
161
  loading.classList.add('hidden');
111
162
  }, 5000);
163
+
164
+ // Function to send theme and auth to iframe
165
+ function sendDataToIframe() {
166
+ // console.log('[Parent] 📤 sendDataToIframe() called');
167
+
168
+ if (!iframe || !iframe.contentWindow) {
169
+ console.error('[Parent] iframe or contentWindow not available');
170
+ return;
171
+ }
172
+
173
+ const htmlElement = document.documentElement;
174
+ const isDark = htmlElement.classList.contains('dark');
175
+ // console.log('[Parent] 🎨 HTML element classes:', htmlElement.className);
176
+ // console.log('[Parent] 🎨 isDark:', isDark);
177
+
178
+ // Get adminTheme value from Alpine.js data if available
179
+ let themeMode = 'auto';
180
+ // console.log('[Parent] 🔍 Checking Alpine.js:', {
181
+ // hasAlpine: !!window.Alpine,
182
+ // hasAlpineData: !!(window.Alpine && window.Alpine.$data)
183
+ // });
184
+
185
+ if (window.Alpine && window.Alpine.$data) {
186
+ try {
187
+ const alpineData = window.Alpine.$data(htmlElement);
188
+ // console.log('[Parent] 🔍 Alpine data:', alpineData);
189
+ if (alpineData && alpineData.adminTheme) {
190
+ themeMode = alpineData.adminTheme;
191
+ // console.log('[Parent] ✅ Got adminTheme from Alpine:', themeMode);
192
+ }
193
+ } catch (e) {
194
+ console.warn('[Parent] Failed to get Alpine data:', e);
195
+ }
196
+ }
197
+
198
+ // Get JWT tokens from localStorage (Django injected them)
199
+ const authToken = localStorage.getItem('auth_token');
200
+ const refreshToken = localStorage.getItem('refresh_token');
201
+ // console.log('[Parent] 🔑 Tokens in localStorage:', {
202
+ // hasAuthToken: !!authToken,
203
+ // hasRefreshToken: !!refreshToken,
204
+ // authTokenLength: authToken ? authToken.length : 0
205
+ // });
206
+
207
+ // Send theme
208
+ const themeMessage = {
209
+ type: 'parent-theme',
210
+ data: {
211
+ theme: isDark ? 'dark' : 'light',
212
+ themeMode: themeMode
213
+ }
214
+ };
215
+ // console.log('[Parent] 📨 Sending theme message:', themeMessage);
216
+
217
+ try {
218
+ iframe.contentWindow.postMessage(themeMessage, iframeOrigin);
219
+ // console.log('[Parent] ✅ Theme message sent successfully to', iframeOrigin);
220
+ } catch (e) {
221
+ console.error('[Parent] Failed to send theme message:', e);
222
+ }
223
+
224
+ // Send auth tokens if available
225
+ if (authToken) {
226
+ const authMessage = {
227
+ type: 'parent-auth',
228
+ data: {
229
+ authToken: authToken,
230
+ refreshToken: refreshToken
231
+ }
232
+ };
233
+ // console.log('[Parent] 📨 Sending auth message:', {
234
+ // type: authMessage.type,
235
+ // hasAuthToken: !!authMessage.data.authToken,
236
+ // hasRefreshToken: !!authMessage.data.refreshToken
237
+ // });
238
+
239
+ try {
240
+ iframe.contentWindow.postMessage(authMessage, iframeOrigin);
241
+ // console.log('[Parent] ✅ Auth message sent successfully to', iframeOrigin);
242
+ } catch (e) {
243
+ console.error('[Parent] Failed to send auth message:', e);
244
+ }
245
+ }
246
+ // else {
247
+ // console.warn('[Parent] ⚠️ No auth token found, skipping auth message');
248
+ // }
249
+ }
250
+
251
+ // Watch for theme changes on HTML element
252
+ // console.log('[Parent] 👀 Setting up MutationObserver for theme changes');
253
+ const observer = new MutationObserver((mutations) => {
254
+ mutations.forEach((mutation) => {
255
+ if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
256
+ // console.log('[Parent] 🔄 HTML class changed, sending updated theme');
257
+ sendDataToIframe();
258
+ }
259
+ });
260
+ });
261
+
262
+ observer.observe(document.documentElement, {
263
+ attributes: true,
264
+ attributeFilter: ['class']
265
+ });
266
+ // console.log('[Parent] ✅ MutationObserver active');
267
+
268
+ // Listen for messages from iframe
269
+ // console.log('[Parent] 👂 Setting up message listener for iframe');
270
+ window.addEventListener('message', (event) => {
271
+ // console.log('[Parent] 📨 Message received from iframe:', {
272
+ // origin: event.origin,
273
+ // expectedOrigin: iframeOrigin,
274
+ // type: event.data?.type,
275
+ // data: event.data?.data
276
+ // });
277
+
278
+ if (event.origin !== iframeOrigin) {
279
+ // console.log('[Parent] ⚠️ Message from unexpected origin, ignoring');
280
+ return;
281
+ }
282
+
283
+ const { type, data } = event.data || {};
284
+
285
+ switch (type) {
286
+ case 'iframe-ready':
287
+ // console.log('[Parent] ✅ iframe-ready received:', data);
288
+ // Send data immediately when iframe is ready
289
+ // console.log('[Parent] 📤 Responding to iframe-ready by sending data');
290
+ sendDataToIframe();
291
+ break;
292
+
293
+ case 'iframe-auth-status':
294
+ // iframe sent auth status update
295
+ console.log('[Parent] 🔐 Auth status received:', data);
296
+ console.log('[Parent] 📊 Current iframe state BEFORE:', {
297
+ visibility: window.getComputedStyle(iframe).visibility,
298
+ opacity: window.getComputedStyle(iframe).opacity,
299
+ hasLoadedClass: iframe.classList.contains('loaded')
300
+ });
301
+
302
+ if (data?.isAuthenticated && !data?.isLoading) {
303
+ // User is authenticated, show iframe
304
+ console.log('[Parent] ✅ Adding .loaded class');
305
+ loading.classList.add('hidden');
306
+ iframe.classList.add('loaded');
307
+
308
+ setTimeout(() => {
309
+ console.log('[Parent] 📊 iframe state AFTER .loaded:', {
310
+ visibility: window.getComputedStyle(iframe).visibility,
311
+ opacity: window.getComputedStyle(iframe).opacity,
312
+ hasLoadedClass: iframe.classList.contains('loaded')
313
+ });
314
+ }, 100);
315
+ } else if (data?.isLoading) {
316
+ // Still loading
317
+ console.log('[Parent] ⏳ Auth loading...');
318
+ loadingText.textContent = 'Loading authentication...';
319
+ } else {
320
+ // Not authenticated
321
+ console.log('[Parent] ⚠️ Not authenticated');
322
+ loadingText.textContent = 'Waiting for authentication...';
323
+ }
324
+ break;
325
+ case 'iframe-resize':
326
+ // Update iframe height based on content (both increase and decrease)
327
+ if (data?.height && typeof data.height === 'number') {
328
+ // Add padding to avoid scroll bars (50px buffer for safety)
329
+ const newHeight = Math.max(600, data.height + 50); // Min 600px
330
+ iframe.style.height = newHeight + 'px';
331
+ }
332
+ break;
333
+ case 'iframe-navigation':
334
+ // console.log('[Parent] 🧭 iframe navigated to:', data.path);
335
+ break;
336
+ default:
337
+ // if (type) {
338
+ // console.log('[Parent] ℹ️ Unknown message type:', type);
339
+ // }
340
+ break;
341
+ }
342
+ });
343
+ // console.log('[Parent] ✅ Message listener active');
112
344
  })();
113
345
  </script>
114
346
  {% endblock %}
@@ -0,0 +1,5 @@
1
+ {% comment %}
2
+ Commands Section - Legacy Template
3
+ Dashboard is now rendered by Next.js in the iframe.
4
+ {% endcomment %}
5
+ <div class="hidden"></div>
@@ -0,0 +1,5 @@
1
+ {% comment %}
2
+ Documentation Section - Legacy Template
3
+ Dashboard is now rendered by Next.js in the iframe.
4
+ {% endcomment %}
5
+ <div class="hidden"></div>
@@ -0,0 +1,5 @@
1
+ {% comment %}
2
+ Overview Section - Legacy Template
3
+ Dashboard is now rendered by Next.js in the iframe.
4
+ {% endcomment %}
5
+ <div class="hidden"></div>
@@ -0,0 +1,5 @@
1
+ {% comment %}
2
+ Stats Section - Legacy Template
3
+ Dashboard is now rendered by Next.js in the iframe.
4
+ {% endcomment %}
5
+ <div class="hidden"></div>
@@ -0,0 +1,5 @@
1
+ {% comment %}
2
+ System Section - Legacy Template
3
+ Dashboard is now rendered by Next.js in the iframe.
4
+ {% endcomment %}
5
+ <div class="hidden"></div>
@@ -0,0 +1,11 @@
1
+ {% comment %}
2
+ Widgets Section - Legacy Template
3
+ This template is kept for backward compatibility with django_dashboard callbacks.
4
+ The actual dashboard is now rendered by Next.js in the iframe.
5
+ {% endcomment %}
6
+
7
+ <!-- Empty section - widgets are rendered in Next.js iframe -->
8
+ <div class="hidden">
9
+ <!-- This section is intentionally empty -->
10
+ <!-- Dashboard widgets are now rendered by Next.js Admin Panel -->
11
+ </div>
@@ -0,0 +1,27 @@
1
+ {% extends "unfold/layouts/skeleton.html" %}
2
+
3
+ {% block extrastyle %}
4
+ {{ block.super }}
5
+ <style id="django-cfg-theme-sync">
6
+ /* ============================================== */
7
+ /* Django CFG - Force Color Variables */
8
+ /* Ensures COLORS from get_color_scheme() apply */
9
+ /* ============================================== */
10
+
11
+ /*
12
+ * NOTE: Colors are already defined via UNFOLD['COLORS'] in:
13
+ * django_cfg/modules/django_unfold/models/config.py:get_color_scheme()
14
+ *
15
+ * This CSS just ensures Tailwind classes use those variables correctly.
16
+ */
17
+
18
+ /* Ensure color-scheme is set for proper dark mode rendering */
19
+ html.dark {
20
+ color-scheme: dark;
21
+ }
22
+
23
+ html:not(.dark) {
24
+ color-scheme: light;
25
+ }
26
+ </style>
27
+ {% endblock %}
@@ -4,6 +4,7 @@ Django-CFG Template Tags
4
4
  Provides template tags for accessing django-cfg configuration constants.
5
5
  """
6
6
 
7
+ import os
7
8
  from django import template
8
9
  from django.utils.safestring import mark_safe
9
10
  from rest_framework_simplejwt.tokens import RefreshToken
@@ -134,3 +135,55 @@ def inject_jwt_tokens_script(context):
134
135
  }})();
135
136
  </script>"""
136
137
  return mark_safe(script)
138
+
139
+
140
+ @register.simple_tag
141
+ def nextjs_admin_url(path=''):
142
+ """
143
+ Get the URL for Next.js Admin Panel.
144
+
145
+ In development mode (when DJANGO_CFG_FRONTEND_DEV_MODE=true):
146
+ Returns http://localhost:3000/{path}
147
+
148
+ In production mode:
149
+ Returns /cfg/admin/{path}
150
+
151
+ Usage in template:
152
+ {% load django_cfg %}
153
+ <iframe src="{% nextjs_admin_url 'private' %}"></iframe>
154
+
155
+ Environment variable:
156
+ DJANGO_CFG_FRONTEND_DEV_MODE=true # Enable dev mode
157
+ DJANGO_CFG_FRONTEND_DEV_PORT=3000 # Custom dev port (default: 3000)
158
+ """
159
+ # Check if frontend dev mode is enabled
160
+ is_dev_mode = os.environ.get('DJANGO_CFG_FRONTEND_DEV_MODE', '').lower() in ('true', '1', 'yes')
161
+
162
+ # Normalize path - ensure trailing slash for Django static view
163
+ if path and not path.endswith('/'):
164
+ path = f'{path}/'
165
+
166
+ if is_dev_mode:
167
+ # Development mode: use Next.js dev server
168
+ dev_port = os.environ.get('DJANGO_CFG_FRONTEND_DEV_PORT', '3000')
169
+ base_url = f'http://localhost:{dev_port}'
170
+ return f'{base_url}/{path}' if path else base_url
171
+ else:
172
+ # Production mode: use Django static files
173
+ return f'/cfg/admin/{path}' if path else '/cfg/admin/'
174
+
175
+
176
+ @register.simple_tag
177
+ def is_frontend_dev_mode():
178
+ """
179
+ Check if frontend is in development mode.
180
+
181
+ Returns True if DJANGO_CFG_FRONTEND_DEV_MODE environment variable is set to true.
182
+
183
+ Usage in template:
184
+ {% load django_cfg %}
185
+ {% if is_frontend_dev_mode %}
186
+ <div class="dev-badge">Dev Mode</div>
187
+ {% endif %}
188
+ """
189
+ return os.environ.get('DJANGO_CFG_FRONTEND_DEV_MODE', '').lower() in ('true', '1', 'yes')
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-cfg
3
- Version: 1.4.84
3
+ Version: 1.4.85
4
4
  Summary: Django AI framework with built-in agents, type-safe Pydantic v2 configuration, and 8 enterprise apps. Replace settings.py, validate at startup, 90% less code. Production-ready AI workflows for Django.
5
5
  Project-URL: Homepage, https://djangocfg.com
6
6
  Project-URL: Documentation, https://djangocfg.com