django-cfg 1.4.104__py3-none-any.whl → 1.4.105__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 CHANGED
@@ -32,7 +32,7 @@ Example:
32
32
  default_app_config = "django_cfg.apps.DjangoCfgConfig"
33
33
 
34
34
  # Version information
35
- __version__ = "1.4.104"
35
+ __version__ = "1.4.105"
36
36
  __license__ = "MIT"
37
37
 
38
38
  # Import registry for organized lazy loading
django_cfg/pyproject.toml CHANGED
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "django-cfg"
7
- version = "1.4.104"
7
+ version = "1.4.105"
8
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
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",]
@@ -180,6 +180,63 @@ resetIframe(tab) {
180
180
  **Why Reset?**
181
181
  This ensures that when users switch back to a tab, it starts from the home page rather than whatever route they navigated to previously.
182
182
 
183
+ ### Open in New Window
184
+
185
+ The External Admin tab (Tab 2) includes an "Open in New Window" button that allows users to break out of the iframe and work in a dedicated browser window/tab:
186
+
187
+ ```javascript
188
+ openInNewWindow() {
189
+ // Get the current iframe URL for the External Admin tab
190
+ const iframe = document.getElementById('nextjs-dashboard-iframe-nextjs');
191
+ if (iframe) {
192
+ const currentUrl = iframe.src || iframe.getAttribute('data-original-src');
193
+ if (currentUrl) {
194
+ window.open(currentUrl, '_blank', 'noopener,noreferrer');
195
+ }
196
+ }
197
+ }
198
+ ```
199
+
200
+ **Features:**
201
+ - Only visible when External Admin tab is active (`x-show="activeTab === 'nextjs'"`)
202
+ - Opens current iframe URL in new window with `noopener,noreferrer` security flags
203
+ - Preserves current route via `postMessage` tracking (see below)
204
+ - Styled as action button with icon and text label
205
+
206
+ **How Route Tracking Works:**
207
+
208
+ In **production** (same-origin), `iframe.src` updates automatically:
209
+ ```javascript
210
+ // iframe.src reflects current URL automatically
211
+ window.open(iframe.src, '_blank');
212
+ ```
213
+
214
+ In **development** (cross-origin), we track navigation via `postMessage`:
215
+ ```javascript
216
+ // iframe sends navigation events
217
+ case 'iframe-navigation':
218
+ if (data?.path) {
219
+ alpineData.currentNextjsPath = data.path; // Track path
220
+ }
221
+
222
+ // Button uses tracked path
223
+ openInNewWindow() {
224
+ const url = new URL(baseUrl);
225
+ url.pathname = this.currentNextjsPath; // Apply tracked path
226
+ window.open(url.toString(), '_blank');
227
+ }
228
+ ```
229
+
230
+ **Why postMessage?**
231
+ Cross-origin iframes cannot access `iframe.src` due to browser security (CORS). The iframe must explicitly send navigation events via `postMessage`.
232
+
233
+ **Why This is Useful:**
234
+ - Full browser features (address bar, bookmarks, etc.)
235
+ - No iframe sandbox restrictions
236
+ - Easier debugging (browser DevTools)
237
+ - Better for complex workflows that require multiple windows
238
+ - Copy/paste and other browser features work better
239
+
183
240
  ## Static File Serving
184
241
 
185
242
  ### Built-in Admin (Tab 1)
@@ -136,6 +136,7 @@
136
136
  <div x-data="{
137
137
  activeTab: 'builtin',
138
138
  previousTab: 'builtin',
139
+ currentNextjsPath: '',
139
140
  switchTab(tab) {
140
141
  if (this.previousTab !== tab) {
141
142
  // Reset iframe to initial URL when switching tabs
@@ -152,6 +153,34 @@
152
153
  const originalSrc = iframe.getAttribute('data-original-src') || iframe.src;
153
154
  iframe.src = originalSrc;
154
155
  }
156
+ // Reset path when resetting iframe
157
+ if (tab === 'nextjs') {
158
+ this.currentNextjsPath = '';
159
+ }
160
+ },
161
+ openInNewWindow() {
162
+ // Get the current iframe URL for the External Admin tab
163
+ const iframe = document.getElementById('nextjs-dashboard-iframe-nextjs');
164
+ if (!iframe) return;
165
+
166
+ // Get base URL from iframe src or data-original-src
167
+ let baseUrl = iframe.src || iframe.getAttribute('data-original-src');
168
+
169
+ // If we have a tracked path from postMessage, use it
170
+ if (this.currentNextjsPath) {
171
+ try {
172
+ const url = new URL(baseUrl);
173
+ // Replace pathname with tracked path
174
+ url.pathname = this.currentNextjsPath;
175
+ baseUrl = url.toString();
176
+ } catch (e) {
177
+ console.warn('[Django-CFG] Failed to construct URL with path:', e);
178
+ }
179
+ }
180
+
181
+ if (baseUrl) {
182
+ window.open(baseUrl, '_blank', 'noopener,noreferrer');
183
+ }
155
184
  }
156
185
  }">
157
186
  {% if is_frontend_dev_mode %}
@@ -200,12 +229,25 @@
200
229
  </button>
201
230
  </nav>
202
231
 
203
- <!-- Version info -->
204
- <div class="py-4 text-xs text-gray-400 dark:text-gray-500">
205
- {% load django_cfg %}
206
- <a href="{% lib_site_url %}" class="text-blue-600 hover:text-blue-700">
207
- {% lib_name %}
208
- </a>
232
+ <!-- Actions & Version info -->
233
+ <div class="flex items-center gap-4 py-4">
234
+ <!-- Open in new window button (only for External Admin tab) -->
235
+ <button @click="openInNewWindow()"
236
+ x-show="activeTab === 'nextjs'"
237
+ title="Open in new window"
238
+ class="flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-md text-gray-600 hover:text-gray-900 hover:bg-gray-100 dark:text-gray-400 dark:hover:text-gray-100 dark:hover:bg-gray-700 transition-all duration-150"
239
+ style="display: none;">
240
+ <span class="material-icons" style="font-size: 16px;">open_in_new</span>
241
+ <span>Open in New Window</span>
242
+ </button>
243
+
244
+ <!-- Version info -->
245
+ <div class="text-xs text-gray-400 dark:text-gray-500">
246
+ {% load django_cfg %}
247
+ <a href="{% lib_site_url %}" class="text-blue-600 hover:text-blue-700">
248
+ {% lib_name %}
249
+ </a>
250
+ </div>
209
251
  </div>
210
252
  </div>
211
253
  </div>
@@ -419,7 +461,18 @@
419
461
  break;
420
462
 
421
463
  case 'iframe-navigation':
422
- // console.log('[Django-CFG] iframe navigated to:', data.path);
464
+ console.log('[Django-CFG] iframe navigated to:', data?.path);
465
+ // Track current path for "Open in New Window" button
466
+ if (iframe.id === 'nextjs-dashboard-iframe-nextjs' && data?.path) {
467
+ // Update Alpine.js data
468
+ const alpineEl = document.querySelector('[x-data]');
469
+ if (alpineEl && window.Alpine) {
470
+ const alpineData = window.Alpine.$data(alpineEl);
471
+ if (alpineData) {
472
+ alpineData.currentNextjsPath = data.path;
473
+ }
474
+ }
475
+ }
423
476
  break;
424
477
 
425
478
  default:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-cfg
3
- Version: 1.4.104
3
+ Version: 1.4.105
4
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
@@ -1,5 +1,5 @@
1
1
  django_cfg/README.md,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- django_cfg/__init__.py,sha256=ItTrBwFktgJmnyLbNZzvM_-noAuHcKsnaiWlUIBy8tc,1621
2
+ django_cfg/__init__.py,sha256=MXUS0jQw0iZtqEX_dQus7hIBxuCa6q1o31FhWBRFJOo,1621
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
@@ -1074,8 +1074,8 @@ django_cfg/static/js/api/support/index.mjs,sha256=oPA3iGkUWYyKQuJlI5-tSxD3AOhwlA
1074
1074
  django_cfg/static/js/api/tasks/client.mjs,sha256=tIy8K-finXzTUL9kOo_L4Q1kchDaHyuzjwS4VymiWPM,3579
1075
1075
  django_cfg/static/js/api/tasks/index.mjs,sha256=yCY1GzdD-RtFZ3pAfk1l0msgO1epyo0lsGCjH0g1Afc,294
1076
1076
  django_cfg/templates/__init__.py,sha256=IzLjt-a7VIJ0OutmAE1_-w0_LpL2u0MgGpnIabjZuW8,19
1077
- django_cfg/templates/admin/DUAL_TAB_ARCHITECTURE.md,sha256=kDUOgLjEv6OdtZNadtQuPhNQmeEZ_TVNt6PKGF6abw4,15607
1078
- django_cfg/templates/admin/index.html,sha256=ygX2GklrPx9yEzk507Kk7Z56zDfaxjMHpq-TqQCN_1M,17743
1077
+ django_cfg/templates/admin/DUAL_TAB_ARCHITECTURE.md,sha256=CL8E3K4rFpXeQiNgrYSMvCW1y-eFaoXxxsI58zPf9dY,17562
1078
+ django_cfg/templates/admin/index.html,sha256=T8atxHnk7f6hiRDA4SKhpRY76srDB-auLANmjSSbejs,20654
1079
1079
  django_cfg/templates/emails/base_email.html,sha256=TWcvYa2IHShlF_E8jf1bWZStRO0v8G4L_GexPxvz6XQ,8836
1080
1080
  django_cfg/templates/unfold/layouts/skeleton.html,sha256=2ArkcNZ34mFs30cOAsTQ1EZiDXcB0aVxkO71lJq9SLE,718
1081
1081
  django_cfg/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -1087,9 +1087,9 @@ django_cfg/utils/version_check.py,sha256=WO51J2m2e-wVqWCRwbultEwu3q1lQasV67Mw2aa
1087
1087
  django_cfg/CHANGELOG.md,sha256=jtT3EprqEJkqSUh7IraP73vQ8PmKUMdRtznQsEnqDZk,2052
1088
1088
  django_cfg/CONTRIBUTING.md,sha256=DU2kyQ6PU0Z24ob7O_OqKWEYHcZmJDgzw-lQCmu6uBg,3041
1089
1089
  django_cfg/LICENSE,sha256=xHuytiUkSZCRG3N11nk1X6q1_EGQtv6aL5O9cqNRhKE,1071
1090
- django_cfg/pyproject.toml,sha256=O8Oqq9_wzDfzRKTjXX4nGoDo1ahLFBKmbs4mO1vDppI,8573
1091
- django_cfg-1.4.104.dist-info/METADATA,sha256=Iy1nZQI6Q43wXqhiHxUq3PPtrnHd-H5C09eoNouOGms,23734
1092
- django_cfg-1.4.104.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
1093
- django_cfg-1.4.104.dist-info/entry_points.txt,sha256=Ucmde4Z2wEzgb4AggxxZ0zaYDb9HpyE5blM3uJ0_VNg,56
1094
- django_cfg-1.4.104.dist-info/licenses/LICENSE,sha256=xHuytiUkSZCRG3N11nk1X6q1_EGQtv6aL5O9cqNRhKE,1071
1095
- django_cfg-1.4.104.dist-info/RECORD,,
1090
+ django_cfg/pyproject.toml,sha256=mSzm-YWCFBLu53-OiCS9-wjIBxA9vm-yMg6Dii7FzYc,8573
1091
+ django_cfg-1.4.105.dist-info/METADATA,sha256=mQjCmuDpR7D-vMY12IAIzJskSnrlBEBNWQ2Fwn-vAEc,23734
1092
+ django_cfg-1.4.105.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
1093
+ django_cfg-1.4.105.dist-info/entry_points.txt,sha256=Ucmde4Z2wEzgb4AggxxZ0zaYDb9HpyE5blM3uJ0_VNg,56
1094
+ django_cfg-1.4.105.dist-info/licenses/LICENSE,sha256=xHuytiUkSZCRG3N11nk1X6q1_EGQtv6aL5O9cqNRhKE,1071
1095
+ django_cfg-1.4.105.dist-info/RECORD,,