django-cfg 1.5.1__py3-none-any.whl → 1.5.2__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/dashboard/TRANSACTION_FIX.md +73 -0
- django_cfg/apps/dashboard/serializers/__init__.py +0 -12
- django_cfg/apps/dashboard/serializers/activity.py +1 -1
- django_cfg/apps/dashboard/services/__init__.py +0 -2
- django_cfg/apps/dashboard/services/charts_service.py +4 -3
- django_cfg/apps/dashboard/services/statistics_service.py +11 -2
- django_cfg/apps/dashboard/services/system_health_service.py +64 -106
- django_cfg/apps/dashboard/urls.py +0 -2
- django_cfg/apps/dashboard/views/__init__.py +0 -2
- django_cfg/apps/dashboard/views/commands_views.py +3 -6
- django_cfg/apps/dashboard/views/overview_views.py +14 -13
- django_cfg/apps/knowbase/apps.py +2 -2
- django_cfg/apps/maintenance/admin/api_key_admin.py +2 -3
- django_cfg/apps/newsletter/admin/newsletter_admin.py +12 -11
- django_cfg/apps/rq/__init__.py +9 -0
- django_cfg/apps/rq/apps.py +80 -0
- django_cfg/apps/rq/management/__init__.py +1 -0
- django_cfg/apps/rq/management/commands/__init__.py +1 -0
- django_cfg/apps/rq/management/commands/rqscheduler.py +31 -0
- django_cfg/apps/rq/management/commands/rqstats.py +33 -0
- django_cfg/apps/rq/management/commands/rqworker.py +31 -0
- django_cfg/apps/rq/management/commands/rqworker_pool.py +27 -0
- django_cfg/apps/rq/serializers/__init__.py +40 -0
- django_cfg/apps/rq/serializers/health.py +60 -0
- django_cfg/apps/rq/serializers/job.py +100 -0
- django_cfg/apps/rq/serializers/queue.py +80 -0
- django_cfg/apps/rq/serializers/schedule.py +178 -0
- django_cfg/apps/rq/serializers/testing.py +139 -0
- django_cfg/apps/rq/serializers/worker.py +58 -0
- django_cfg/apps/rq/services/__init__.py +25 -0
- django_cfg/apps/rq/services/config_helper.py +233 -0
- django_cfg/apps/rq/services/models/README.md +417 -0
- django_cfg/apps/rq/services/models/__init__.py +30 -0
- django_cfg/apps/rq/services/models/event.py +123 -0
- django_cfg/apps/rq/services/models/job.py +99 -0
- django_cfg/apps/rq/services/models/queue.py +92 -0
- django_cfg/apps/rq/services/models/worker.py +104 -0
- django_cfg/apps/rq/services/rq_converters.py +183 -0
- django_cfg/apps/rq/tasks/__init__.py +23 -0
- django_cfg/apps/rq/tasks/demo_tasks.py +284 -0
- django_cfg/apps/rq/urls.py +54 -0
- django_cfg/apps/rq/views/__init__.py +19 -0
- django_cfg/apps/rq/views/jobs.py +882 -0
- django_cfg/apps/rq/views/monitoring.py +248 -0
- django_cfg/apps/rq/views/queues.py +261 -0
- django_cfg/apps/rq/views/schedule.py +400 -0
- django_cfg/apps/rq/views/testing.py +761 -0
- django_cfg/apps/rq/views/workers.py +195 -0
- django_cfg/apps/urls.py +6 -7
- django_cfg/core/base/config_model.py +10 -26
- django_cfg/core/builders/apps_builder.py +4 -11
- django_cfg/core/generation/integration_generators/__init__.py +3 -6
- django_cfg/core/generation/integration_generators/django_rq.py +80 -0
- django_cfg/core/generation/orchestrator.py +9 -19
- django_cfg/core/integration/display/startup.py +6 -20
- django_cfg/mixins/__init__.py +2 -0
- django_cfg/mixins/superadmin_api.py +59 -0
- django_cfg/models/__init__.py +3 -3
- django_cfg/models/django/__init__.py +3 -3
- django_cfg/models/django/django_rq.py +621 -0
- django_cfg/models/django/revolution_legacy.py +1 -1
- django_cfg/modules/base.py +4 -6
- django_cfg/modules/django_admin/config/background_task_config.py +4 -4
- django_cfg/modules/django_admin/utils/html/composition.py +9 -2
- django_cfg/modules/django_unfold/navigation.py +1 -26
- django_cfg/pyproject.toml +4 -4
- django_cfg/registry/core.py +4 -7
- django_cfg/static/frontend/admin.zip +0 -0
- django_cfg/templates/admin/constance/includes/results_list.html +73 -0
- django_cfg/templates/admin/index.html +187 -62
- django_cfg/templatetags/django_cfg.py +61 -1
- {django_cfg-1.5.1.dist-info → django_cfg-1.5.2.dist-info}/METADATA +5 -6
- {django_cfg-1.5.1.dist-info → django_cfg-1.5.2.dist-info}/RECORD +77 -82
- django_cfg/apps/dashboard/permissions.py +0 -48
- django_cfg/apps/dashboard/serializers/django_q2.py +0 -50
- django_cfg/apps/dashboard/services/django_q2_service.py +0 -159
- django_cfg/apps/dashboard/views/django_q2_views.py +0 -79
- django_cfg/apps/tasks/__init__.py +0 -64
- django_cfg/apps/tasks/admin/__init__.py +0 -4
- django_cfg/apps/tasks/admin/config.py +0 -98
- django_cfg/apps/tasks/admin/task_log.py +0 -238
- django_cfg/apps/tasks/apps.py +0 -15
- django_cfg/apps/tasks/filters/__init__.py +0 -10
- django_cfg/apps/tasks/filters/task_log.py +0 -121
- django_cfg/apps/tasks/migrations/0001_initial.py +0 -196
- django_cfg/apps/tasks/migrations/0002_delete_tasklog.py +0 -16
- django_cfg/apps/tasks/migrations/__init__.py +0 -0
- django_cfg/apps/tasks/models/__init__.py +0 -4
- django_cfg/apps/tasks/models/task_log.py +0 -246
- django_cfg/apps/tasks/serializers/__init__.py +0 -28
- django_cfg/apps/tasks/serializers/task_log.py +0 -249
- django_cfg/apps/tasks/services/__init__.py +0 -10
- django_cfg/apps/tasks/services/client/__init__.py +0 -7
- django_cfg/apps/tasks/services/client/client.py +0 -234
- django_cfg/apps/tasks/services/config_helper.py +0 -63
- django_cfg/apps/tasks/services/sync.py +0 -204
- django_cfg/apps/tasks/urls.py +0 -16
- django_cfg/apps/tasks/views/__init__.py +0 -10
- django_cfg/apps/tasks/views/task_log.py +0 -41
- django_cfg/apps/tasks/views/task_log_base.py +0 -41
- django_cfg/apps/tasks/views/task_log_overview.py +0 -100
- django_cfg/apps/tasks/views/task_log_related.py +0 -41
- django_cfg/apps/tasks/views/task_log_stats.py +0 -91
- django_cfg/apps/tasks/views/task_log_timeline.py +0 -81
- django_cfg/core/generation/integration_generators/django_q2.py +0 -133
- django_cfg/core/generation/integration_generators/tasks.py +0 -88
- django_cfg/models/django/django_q2.py +0 -514
- django_cfg/models/tasks/__init__.py +0 -49
- django_cfg/models/tasks/backends.py +0 -122
- django_cfg/models/tasks/config.py +0 -209
- django_cfg/models/tasks/utils.py +0 -162
- django_cfg/modules/django_q2/README.md +0 -140
- django_cfg/modules/django_q2/__init__.py +0 -8
- django_cfg/modules/django_q2/apps.py +0 -107
- django_cfg/modules/django_q2/management/__init__.py +0 -0
- django_cfg/modules/django_q2/management/commands/__init__.py +0 -0
- django_cfg/modules/django_q2/management/commands/sync_django_q_schedules.py +0 -74
- {django_cfg-1.5.1.dist-info → django_cfg-1.5.2.dist-info}/WHEEL +0 -0
- {django_cfg-1.5.1.dist-info → django_cfg-1.5.2.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.5.1.dist-info → django_cfg-1.5.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -8,6 +8,25 @@
|
|
|
8
8
|
{{ block.super }}
|
|
9
9
|
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
|
10
10
|
<link rel="stylesheet" href="{% static 'admin/css/dashboard.css' %}">
|
|
11
|
+
|
|
12
|
+
{# JWT Token Injection for iframe authentication #}
|
|
13
|
+
{% if user.is_authenticated %}
|
|
14
|
+
<script>
|
|
15
|
+
(function() {
|
|
16
|
+
try {
|
|
17
|
+
// console.log('[Django Admin] User is authenticated:', '{{ user.username }}', 'is_staff:', {{ user.is_staff|lower }}, 'is_superuser:', {{ user.is_superuser|lower }});
|
|
18
|
+
{% generate_jwt_tokens as jwt_tokens %}
|
|
19
|
+
// console.log('[Django Admin] JWT tokens generated - access:', '{{ jwt_tokens.access }}'.substring(0, 20) + '...', 'refresh:', '{{ jwt_tokens.refresh }}'.substring(0, 20) + '...');
|
|
20
|
+
localStorage.setItem('auth_token', '{{ jwt_tokens.access }}');
|
|
21
|
+
localStorage.setItem('refresh_token', '{{ jwt_tokens.refresh }}');
|
|
22
|
+
// console.log('[Django Admin] Tokens saved to localStorage');
|
|
23
|
+
} catch (e) {
|
|
24
|
+
console.error('[Django Admin] Failed to inject JWT tokens:', e);
|
|
25
|
+
}
|
|
26
|
+
})();
|
|
27
|
+
</script>
|
|
28
|
+
{% endif %}
|
|
29
|
+
|
|
11
30
|
<style>
|
|
12
31
|
/* Make content container full height and full width */
|
|
13
32
|
#content {
|
|
@@ -224,8 +243,7 @@
|
|
|
224
243
|
{% endif %}
|
|
225
244
|
|
|
226
245
|
<!-- Tab Navigation -->
|
|
227
|
-
{%
|
|
228
|
-
{% if is_external_enabled %}
|
|
246
|
+
{% is_dev as is_development %}
|
|
229
247
|
<div>
|
|
230
248
|
<div class="border-b border-gray-300 dark:border-gray-600" style="border-bottom-color: rgba(209, 213, 219, 0.2);">
|
|
231
249
|
<style>
|
|
@@ -281,6 +299,7 @@
|
|
|
281
299
|
<span class="sm:hidden">Admin</span>
|
|
282
300
|
</button>
|
|
283
301
|
|
|
302
|
+
{% if is_development %}
|
|
284
303
|
<button @click="switchTab('docs')"
|
|
285
304
|
class="whitespace-nowrap py-4 px-2 border-b-2 font-medium text-sm flex items-center gap-2 transition-all duration-200"
|
|
286
305
|
:class="activeTab === 'docs'
|
|
@@ -289,9 +308,10 @@
|
|
|
289
308
|
<span class="material-icons text-base">description</span>
|
|
290
309
|
<span>Docs</span>
|
|
291
310
|
</button>
|
|
311
|
+
{% endif %}
|
|
292
312
|
</nav>
|
|
293
313
|
|
|
294
|
-
<!-- Actions & Version
|
|
314
|
+
<!-- Actions & Version -->
|
|
295
315
|
<div class="flex items-center gap-2 md:gap-4 py-4">
|
|
296
316
|
<!-- Open in new window button (available for all tabs) -->
|
|
297
317
|
<button @click="openInNewWindow()"
|
|
@@ -301,19 +321,19 @@
|
|
|
301
321
|
<span class="hidden sm:inline">Open in New Window</span>
|
|
302
322
|
</button>
|
|
303
323
|
|
|
304
|
-
<!-- Version info -->
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
324
|
+
<!-- Version info (always visible) -->
|
|
325
|
+
{% load django_cfg %}
|
|
326
|
+
<a href="{% lib_site_url %}"
|
|
327
|
+
target="_blank"
|
|
328
|
+
rel="noopener noreferrer"
|
|
329
|
+
class="text-xs font-medium text-gray-600 hover:text-primary-600 dark:text-gray-400 dark:hover:text-primary-500 transition-colors duration-150 whitespace-nowrap">
|
|
330
|
+
{% lib_name %}
|
|
331
|
+
</a>
|
|
311
332
|
</div>
|
|
312
333
|
</div>
|
|
313
334
|
</div>
|
|
314
335
|
</div>
|
|
315
336
|
</div>
|
|
316
|
-
{% endif %}
|
|
317
337
|
|
|
318
338
|
<!-- Built-in Dashboard Tab Content -->
|
|
319
339
|
<div x-show="activeTab === 'builtin'" style="display: block;">
|
|
@@ -335,7 +355,6 @@
|
|
|
335
355
|
</div>
|
|
336
356
|
|
|
337
357
|
<!-- External Next.js Admin Tab Content -->
|
|
338
|
-
{% if is_external_enabled %}
|
|
339
358
|
<div x-show="activeTab === 'nextjs'" style="display: none;">
|
|
340
359
|
<div class="iframe-container">
|
|
341
360
|
<div class="iframe-loading" id="iframe-loading-nextjs">
|
|
@@ -353,9 +372,9 @@
|
|
|
353
372
|
></iframe>
|
|
354
373
|
</div>
|
|
355
374
|
</div>
|
|
356
|
-
{% endif %}
|
|
357
375
|
|
|
358
|
-
<!-- Docs Tab Content -->
|
|
376
|
+
<!-- Docs Tab Content (Development Mode Only) -->
|
|
377
|
+
{% if is_development %}
|
|
359
378
|
<div x-show="activeTab === 'docs'" style="display: none;">
|
|
360
379
|
<div class="iframe-container">
|
|
361
380
|
<div class="iframe-loading" id="iframe-loading-docs">
|
|
@@ -373,6 +392,7 @@
|
|
|
373
392
|
></iframe>
|
|
374
393
|
</div>
|
|
375
394
|
</div>
|
|
395
|
+
{% endif %}
|
|
376
396
|
</div>
|
|
377
397
|
{% endcomponent %}
|
|
378
398
|
{% endblock %}
|
|
@@ -383,6 +403,8 @@
|
|
|
383
403
|
(function() {
|
|
384
404
|
'use strict';
|
|
385
405
|
|
|
406
|
+
// console.log('[Django-CFG] Script loading... readyState:', document.readyState);
|
|
407
|
+
|
|
386
408
|
/**
|
|
387
409
|
* MessageBridge - Handles postMessage communication with internal Next.js iframes
|
|
388
410
|
*/
|
|
@@ -397,20 +419,30 @@
|
|
|
397
419
|
* Send theme data to iframe
|
|
398
420
|
*/
|
|
399
421
|
sendTheme() {
|
|
400
|
-
if (!this.iframe?.contentWindow)
|
|
422
|
+
if (!this.iframe?.contentWindow) {
|
|
423
|
+
console.warn('[Django-CFG] sendTheme: iframe contentWindow not available');
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
401
426
|
|
|
402
427
|
const htmlElement = document.documentElement;
|
|
403
428
|
const isDark = htmlElement.classList.contains('dark');
|
|
404
429
|
const themeMode = this._getThemeMode();
|
|
405
430
|
|
|
431
|
+
// console.log('[Django-CFG] sendTheme: htmlElement classes:', htmlElement.className);
|
|
432
|
+
// console.log('[Django-CFG] sendTheme: isDark:', isDark, 'themeMode:', themeMode);
|
|
433
|
+
|
|
406
434
|
try {
|
|
435
|
+
// Use '*' in dev mode to handle localhost vs 127.0.0.1 mismatch
|
|
436
|
+
const targetOrigin = '*'; // In production, use specific origin for security
|
|
437
|
+
// console.log('[Django-CFG] Sending parent-theme message to iframe:', this.iframe.id);
|
|
407
438
|
this.iframe.contentWindow.postMessage({
|
|
408
439
|
type: 'parent-theme',
|
|
409
440
|
data: {
|
|
410
441
|
theme: isDark ? 'dark' : 'light',
|
|
411
442
|
themeMode: themeMode
|
|
412
443
|
}
|
|
413
|
-
},
|
|
444
|
+
}, targetOrigin);
|
|
445
|
+
// console.log('[Django-CFG] parent-theme message sent successfully');
|
|
414
446
|
} catch (e) {
|
|
415
447
|
console.error('[Django-CFG] Failed to send theme:', e);
|
|
416
448
|
}
|
|
@@ -420,18 +452,30 @@
|
|
|
420
452
|
* Send auth tokens to iframe
|
|
421
453
|
*/
|
|
422
454
|
sendAuth() {
|
|
423
|
-
if (!this.iframe?.contentWindow)
|
|
455
|
+
if (!this.iframe?.contentWindow) {
|
|
456
|
+
console.warn('[Django-CFG] sendAuth: iframe contentWindow not available');
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
424
459
|
|
|
425
460
|
const authToken = localStorage.getItem('auth_token');
|
|
426
461
|
const refreshToken = localStorage.getItem('refresh_token');
|
|
427
462
|
|
|
428
|
-
|
|
463
|
+
// console.log('[Django-CFG] sendAuth: authToken:', authToken ? authToken.substring(0, 20) + '...' : 'null', 'refreshToken:', refreshToken ? refreshToken.substring(0, 20) + '...' : 'null');
|
|
464
|
+
|
|
465
|
+
if (!authToken) {
|
|
466
|
+
console.warn('[Django-CFG] sendAuth: No auth token found in localStorage');
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
429
469
|
|
|
430
470
|
try {
|
|
471
|
+
// Use '*' in dev mode to handle localhost vs 127.0.0.1 mismatch
|
|
472
|
+
const targetOrigin = '*'; // In production, use specific origin for security
|
|
473
|
+
// console.log('[Django-CFG] Sending parent-auth message to iframe:', this.iframe.id);
|
|
431
474
|
this.iframe.contentWindow.postMessage({
|
|
432
475
|
type: 'parent-auth',
|
|
433
476
|
data: { authToken, refreshToken }
|
|
434
|
-
},
|
|
477
|
+
}, targetOrigin);
|
|
478
|
+
// console.log('[Django-CFG] parent-auth message sent successfully');
|
|
435
479
|
} catch (e) {
|
|
436
480
|
console.error('[Django-CFG] Failed to send auth:', e);
|
|
437
481
|
}
|
|
@@ -441,8 +485,10 @@
|
|
|
441
485
|
* Send all data (theme + auth)
|
|
442
486
|
*/
|
|
443
487
|
sendAllData() {
|
|
488
|
+
// console.log('[Django-CFG] sendAllData called');
|
|
444
489
|
this.sendTheme();
|
|
445
490
|
this.sendAuth();
|
|
491
|
+
// console.log('[Django-CFG] sendAllData completed');
|
|
446
492
|
}
|
|
447
493
|
|
|
448
494
|
/**
|
|
@@ -498,41 +544,33 @@
|
|
|
498
544
|
|
|
499
545
|
init() {
|
|
500
546
|
this._setupLoadHandler();
|
|
501
|
-
|
|
547
|
+
// Message listener now handled globally by IframeManager
|
|
502
548
|
this.messageBridge.watchThemeChanges();
|
|
503
549
|
}
|
|
504
550
|
|
|
505
551
|
_setupLoadHandler() {
|
|
506
552
|
this.iframe.addEventListener('load', () => {
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
_setupMessageListener() {
|
|
514
|
-
this.messageListener = (event) => {
|
|
515
|
-
if (event.origin !== this.origin) return;
|
|
516
|
-
|
|
517
|
-
const { type, data } = event.data || {};
|
|
518
|
-
this._handleMessage(type, data);
|
|
519
|
-
};
|
|
553
|
+
// Hide loading indicator
|
|
554
|
+
if (this.loading) {
|
|
555
|
+
this.loading.classList.add('hidden');
|
|
556
|
+
}
|
|
557
|
+
this.iframe.classList.add('loaded');
|
|
520
558
|
|
|
521
|
-
|
|
559
|
+
// Don't send auth immediately - wait for iframe-ready message
|
|
560
|
+
// The iframe will send 'iframe-ready' when it's mounted and ready to receive messages
|
|
561
|
+
});
|
|
522
562
|
}
|
|
523
563
|
|
|
524
|
-
|
|
564
|
+
handleMessage(type, data) {
|
|
565
|
+
// console.log('[Django-CFG] Received message from iframe:', type, data);
|
|
525
566
|
switch (type) {
|
|
526
567
|
case 'iframe-ready':
|
|
568
|
+
// console.log('[Django-CFG] iframe-ready received, sending auth and theme data');
|
|
527
569
|
this.messageBridge.sendAllData();
|
|
528
570
|
break;
|
|
529
571
|
|
|
530
572
|
case 'iframe-auth-status':
|
|
531
|
-
console.log('[Django-CFG]
|
|
532
|
-
break;
|
|
533
|
-
|
|
534
|
-
case 'iframe-resize':
|
|
535
|
-
this._handleResize();
|
|
573
|
+
// console.log('[Django-CFG] iframe-auth-status:', data);
|
|
536
574
|
break;
|
|
537
575
|
|
|
538
576
|
case 'iframe-navigation':
|
|
@@ -541,19 +579,9 @@
|
|
|
541
579
|
}
|
|
542
580
|
}
|
|
543
581
|
|
|
544
|
-
_handleResize() {
|
|
545
|
-
if (this.iframe.classList.contains('loaded')) return;
|
|
546
|
-
|
|
547
|
-
console.log('[Django-CFG] First resize - showing iframe');
|
|
548
|
-
this.loading?.classList.add('hidden');
|
|
549
|
-
this.iframe.classList.add('loaded');
|
|
550
|
-
}
|
|
551
|
-
|
|
552
582
|
_handleNavigation(path) {
|
|
553
583
|
if (!path) return;
|
|
554
584
|
|
|
555
|
-
console.log('[Django-CFG] Navigation:', path);
|
|
556
|
-
|
|
557
585
|
// Track path for "Open in New Window" (only for nextjs tab)
|
|
558
586
|
if (this.iframe.id === 'nextjs-dashboard-iframe-nextjs') {
|
|
559
587
|
this._updateAlpinePath(path);
|
|
@@ -572,9 +600,7 @@
|
|
|
572
600
|
|
|
573
601
|
destroy() {
|
|
574
602
|
this.messageBridge.destroy();
|
|
575
|
-
|
|
576
|
-
window.removeEventListener('message', this.messageListener);
|
|
577
|
-
}
|
|
603
|
+
// Message listener cleanup handled by IframeManager
|
|
578
604
|
}
|
|
579
605
|
}
|
|
580
606
|
|
|
@@ -596,14 +622,12 @@
|
|
|
596
622
|
|
|
597
623
|
_setupLoadHandler() {
|
|
598
624
|
this.iframe.addEventListener('load', () => {
|
|
599
|
-
console.log('[Django-CFG] External iframe loaded');
|
|
600
625
|
this._showIframe();
|
|
601
626
|
});
|
|
602
627
|
}
|
|
603
628
|
|
|
604
629
|
_setupErrorHandler() {
|
|
605
630
|
this.iframe.addEventListener('error', () => {
|
|
606
|
-
console.error('[Django-CFG] External iframe failed to load');
|
|
607
631
|
this._showError();
|
|
608
632
|
});
|
|
609
633
|
}
|
|
@@ -611,7 +635,6 @@
|
|
|
611
635
|
_setupTimeout() {
|
|
612
636
|
setTimeout(() => {
|
|
613
637
|
if (!this.iframe.classList.contains('loaded')) {
|
|
614
|
-
console.log('[Django-CFG] Timeout - showing iframe anyway');
|
|
615
638
|
this._showIframe();
|
|
616
639
|
}
|
|
617
640
|
}, this.LOAD_TIMEOUT);
|
|
@@ -629,6 +652,11 @@
|
|
|
629
652
|
}
|
|
630
653
|
}
|
|
631
654
|
|
|
655
|
+
handleMessage(type, data) {
|
|
656
|
+
// External iframes don't handle postMessage
|
|
657
|
+
// This is a no-op to prevent errors if called
|
|
658
|
+
}
|
|
659
|
+
|
|
632
660
|
destroy() {
|
|
633
661
|
// No cleanup needed for external iframes
|
|
634
662
|
}
|
|
@@ -640,24 +668,83 @@
|
|
|
640
668
|
class IframeManager {
|
|
641
669
|
constructor() {
|
|
642
670
|
this.handlers = [];
|
|
671
|
+
this.iframeMap = new Map(); // Map iframe contentWindow -> handler
|
|
672
|
+
this._setupGlobalMessageListener();
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
/**
|
|
676
|
+
* Setup ONE global message listener for ALL iframes
|
|
677
|
+
*/
|
|
678
|
+
_setupGlobalMessageListener() {
|
|
679
|
+
// console.log('[Django-CFG] Setting up global message listener');
|
|
680
|
+
window.addEventListener('message', (event) => {
|
|
681
|
+
// console.log('[Django-CFG] Received message:', {
|
|
682
|
+
// type: event.data?.type,
|
|
683
|
+
// origin: event.origin,
|
|
684
|
+
// source: event.source,
|
|
685
|
+
// mapSize: this.iframeMap.size,
|
|
686
|
+
// mapKeys: Array.from(this.iframeMap.keys())
|
|
687
|
+
// });
|
|
688
|
+
|
|
689
|
+
// Find which handler should handle this message
|
|
690
|
+
const handler = this.iframeMap.get(event.source);
|
|
691
|
+
|
|
692
|
+
if (handler) {
|
|
693
|
+
// console.log('[Django-CFG] Routing message to handler:', handler.iframe.id);
|
|
694
|
+
const { type, data } = event.data || {};
|
|
695
|
+
handler.handleMessage(type, data);
|
|
696
|
+
} else {
|
|
697
|
+
// Message from unknown source (browser extension, etc.)
|
|
698
|
+
// console.log('[Django-CFG] Ignoring message from unknown source');
|
|
699
|
+
}
|
|
700
|
+
});
|
|
643
701
|
}
|
|
644
702
|
|
|
645
703
|
/**
|
|
646
704
|
* Register an iframe for management
|
|
647
705
|
*/
|
|
648
706
|
register(iframeId, loadingId, options = {}) {
|
|
707
|
+
// console.log('[Django-CFG] register() called for:', iframeId);
|
|
649
708
|
const iframe = document.getElementById(iframeId);
|
|
650
709
|
const loading = document.getElementById(loadingId);
|
|
651
710
|
|
|
652
711
|
if (!iframe) {
|
|
653
|
-
console.warn(
|
|
712
|
+
console.warn('[Django-CFG] Iframe not found:', iframeId, '- skipping registration');
|
|
654
713
|
return;
|
|
655
714
|
}
|
|
656
715
|
|
|
716
|
+
// console.log('[Django-CFG] Creating handler for iframe:', iframeId);
|
|
657
717
|
const handler = this._createHandler(iframe, loading, options);
|
|
718
|
+
// console.log('[Django-CFG] Handler created:', !!handler, 'for iframe:', iframeId);
|
|
658
719
|
if (handler) {
|
|
720
|
+
// console.log('[Django-CFG] Initializing handler for:', iframeId);
|
|
659
721
|
handler.init();
|
|
660
722
|
this.handlers.push(handler);
|
|
723
|
+
// console.log('[Django-CFG] Handler added to handlers array for:', iframeId);
|
|
724
|
+
|
|
725
|
+
// Register only InternalIframeHandler in the map for message routing
|
|
726
|
+
// ExternalIframeHandler doesn't handle postMessage
|
|
727
|
+
if (handler instanceof InternalIframeHandler) {
|
|
728
|
+
// console.log('[Django-CFG] Handler is InternalIframeHandler, attempting registration for:', iframeId);
|
|
729
|
+
// Try to register immediately if contentWindow is available
|
|
730
|
+
if (iframe.contentWindow) {
|
|
731
|
+
// console.log('[Django-CFG] contentWindow available, registering immediately');
|
|
732
|
+
this.iframeMap.set(iframe.contentWindow, handler);
|
|
733
|
+
// console.log('[Django-CFG] Registered handler immediately for iframe:', iframeId, 'map size:', this.iframeMap.size);
|
|
734
|
+
} else {
|
|
735
|
+
// console.log('[Django-CFG] contentWindow not available yet, waiting for load event');
|
|
736
|
+
// Fallback: wait for load if contentWindow not available yet
|
|
737
|
+
iframe.addEventListener('load', () => {
|
|
738
|
+
if (iframe.contentWindow) {
|
|
739
|
+
// console.log('[Django-CFG] Load event fired, registering handler');
|
|
740
|
+
this.iframeMap.set(iframe.contentWindow, handler);
|
|
741
|
+
// console.log('[Django-CFG] Registered handler on load for iframe:', iframeId, 'map size:', this.iframeMap.size);
|
|
742
|
+
}
|
|
743
|
+
}, { once: true });
|
|
744
|
+
}
|
|
745
|
+
} else {
|
|
746
|
+
// console.log('[Django-CFG] Handler is NOT InternalIframeHandler (is external), skipping map registration for:', iframeId);
|
|
747
|
+
}
|
|
661
748
|
}
|
|
662
749
|
}
|
|
663
750
|
|
|
@@ -665,18 +752,29 @@
|
|
|
665
752
|
* Create appropriate handler based on iframe type
|
|
666
753
|
*/
|
|
667
754
|
_createHandler(iframe, loading, options) {
|
|
755
|
+
// console.log('[Django-CFG] _createHandler for iframe:', iframe.id);
|
|
668
756
|
const src = iframe.src || iframe.getAttribute('src');
|
|
757
|
+
// console.log('[Django-CFG] iframe src:', src);
|
|
669
758
|
if (!src) {
|
|
670
759
|
console.warn('[Django-CFG] Iframe has no src');
|
|
671
760
|
return null;
|
|
672
761
|
}
|
|
673
762
|
|
|
674
763
|
const url = new URL(src, window.location.origin);
|
|
675
|
-
|
|
764
|
+
|
|
765
|
+
// Determine if this is an external iframe (docs, etc.)
|
|
766
|
+
// Dev servers on localhost (ports 3000, 3777) are considered INTERNAL
|
|
767
|
+
// because they need postMessage communication
|
|
768
|
+
const isDevServer = url.hostname === 'localhost' && (url.port === '3000' || url.port === '3777');
|
|
769
|
+
const isExternal = options.external && !isDevServer;
|
|
770
|
+
|
|
771
|
+
// console.log('[Django-CFG] iframe url.origin:', url.origin, 'window.location.origin:', window.location.origin, 'isDevServer:', isDevServer, 'isExternal:', isExternal);
|
|
676
772
|
|
|
677
773
|
if (isExternal) {
|
|
774
|
+
// console.log('[Django-CFG] Creating ExternalIframeHandler for:', iframe.id);
|
|
678
775
|
return new ExternalIframeHandler(iframe, loading);
|
|
679
776
|
} else {
|
|
777
|
+
// console.log('[Django-CFG] Creating InternalIframeHandler for:', iframe.id);
|
|
680
778
|
return new InternalIframeHandler(iframe, loading, url.origin);
|
|
681
779
|
}
|
|
682
780
|
}
|
|
@@ -687,16 +785,43 @@
|
|
|
687
785
|
destroy() {
|
|
688
786
|
this.handlers.forEach(handler => handler.destroy());
|
|
689
787
|
this.handlers = [];
|
|
788
|
+
this.iframeMap.clear();
|
|
690
789
|
}
|
|
691
790
|
}
|
|
692
791
|
|
|
693
792
|
// Initialize iframe manager
|
|
793
|
+
// console.log('[Django-CFG] Initializing IframeManager...');
|
|
694
794
|
const manager = new IframeManager();
|
|
795
|
+
// console.log('[Django-CFG] IframeManager initialized');
|
|
796
|
+
|
|
797
|
+
// Register all iframes after DOM is ready
|
|
798
|
+
// This ensures iframe elements exist before we try to register them
|
|
799
|
+
function registerIframes() {
|
|
800
|
+
// console.log('[Django-CFG] registerIframes() called - DOM ready');
|
|
801
|
+
// console.log('[Django-CFG] Looking for iframes in DOM...');
|
|
802
|
+
|
|
803
|
+
const builtinIframe = document.getElementById('nextjs-dashboard-iframe-builtin');
|
|
804
|
+
const nextjsIframe = document.getElementById('nextjs-dashboard-iframe-nextjs');
|
|
805
|
+
// console.log('[Django-CFG] Found builtin iframe:', !!builtinIframe);
|
|
806
|
+
// console.log('[Django-CFG] Found nextjs iframe:', !!nextjsIframe);
|
|
807
|
+
|
|
808
|
+
manager.register('nextjs-dashboard-iframe-builtin', 'iframe-loading-builtin');
|
|
809
|
+
manager.register('nextjs-dashboard-iframe-nextjs', 'iframe-loading-nextjs');
|
|
810
|
+
{% if is_development %}
|
|
811
|
+
manager.register('nextjs-dashboard-iframe-docs', 'iframe-loading-docs', { external: true });
|
|
812
|
+
{% endif %}
|
|
813
|
+
// console.log('[Django-CFG] Iframe registration completed');
|
|
814
|
+
}
|
|
695
815
|
|
|
696
|
-
// Register
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
816
|
+
// Register iframes when DOM is ready
|
|
817
|
+
// console.log('[Django-CFG] Setting up DOM ready listener, readyState:', document.readyState);
|
|
818
|
+
if (document.readyState === 'loading') {
|
|
819
|
+
// console.log('[Django-CFG] DOM is loading, waiting for DOMContentLoaded...');
|
|
820
|
+
document.addEventListener('DOMContentLoaded', registerIframes);
|
|
821
|
+
} else {
|
|
822
|
+
// console.log('[Django-CFG] DOM already loaded, registering immediately...');
|
|
823
|
+
registerIframes();
|
|
824
|
+
}
|
|
700
825
|
|
|
701
826
|
// Cleanup on page unload
|
|
702
827
|
window.addEventListener('beforeunload', () => {
|
|
@@ -96,6 +96,28 @@ def lib_health_url():
|
|
|
96
96
|
return LIB_HEALTH_URL
|
|
97
97
|
|
|
98
98
|
|
|
99
|
+
@register.simple_tag
|
|
100
|
+
def is_dev():
|
|
101
|
+
"""
|
|
102
|
+
Check if project is in development mode.
|
|
103
|
+
|
|
104
|
+
Returns True if the current DjangoConfig has is_development = True.
|
|
105
|
+
|
|
106
|
+
Usage in template:
|
|
107
|
+
{% load django_cfg %}
|
|
108
|
+
{% is_dev as is_development %}
|
|
109
|
+
{% if is_development %}
|
|
110
|
+
<div>Development Mode</div>
|
|
111
|
+
{% endif %}
|
|
112
|
+
"""
|
|
113
|
+
try:
|
|
114
|
+
from django_cfg.core.config import get_current_config
|
|
115
|
+
config = get_current_config()
|
|
116
|
+
return config and config.is_development
|
|
117
|
+
except Exception:
|
|
118
|
+
return False
|
|
119
|
+
|
|
120
|
+
|
|
99
121
|
@register.simple_tag
|
|
100
122
|
def lib_subtitle():
|
|
101
123
|
"""Get the library subtitle/tagline."""
|
|
@@ -187,7 +209,6 @@ def inject_jwt_tokens_script(context):
|
|
|
187
209
|
// Store JWT tokens in localStorage for Next.js app
|
|
188
210
|
localStorage.setItem('auth_token', '{access_token}');
|
|
189
211
|
localStorage.setItem('refresh_token', '{refresh_token}');
|
|
190
|
-
console.log('JWT tokens injected successfully');
|
|
191
212
|
}} catch (e) {{
|
|
192
213
|
console.error('Failed to inject JWT tokens:', e);
|
|
193
214
|
}}
|
|
@@ -342,3 +363,42 @@ def nextjs_external_admin_title():
|
|
|
342
363
|
return config.nextjs_admin.get_tab_title()
|
|
343
364
|
except Exception:
|
|
344
365
|
return 'Next.js Admin'
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
@register.simple_tag(takes_context=True)
|
|
369
|
+
def generate_jwt_tokens(context):
|
|
370
|
+
"""
|
|
371
|
+
Generate JWT tokens for the current authenticated user.
|
|
372
|
+
|
|
373
|
+
Returns a dict with 'access' and 'refresh' tokens for use in templates.
|
|
374
|
+
|
|
375
|
+
Usage:
|
|
376
|
+
{% load django_cfg %}
|
|
377
|
+
{% generate_jwt_tokens as jwt_tokens %}
|
|
378
|
+
localStorage.setItem('auth_token', '{{ jwt_tokens.access }}');
|
|
379
|
+
localStorage.setItem('refresh_token', '{{ jwt_tokens.refresh }}');
|
|
380
|
+
"""
|
|
381
|
+
from django.contrib.auth.models import AnonymousUser
|
|
382
|
+
|
|
383
|
+
request = context.get('request')
|
|
384
|
+
if not request:
|
|
385
|
+
return {'access': '', 'refresh': ''}
|
|
386
|
+
|
|
387
|
+
user = getattr(request, 'user', None)
|
|
388
|
+
if not user or isinstance(user, AnonymousUser) or not user.is_authenticated:
|
|
389
|
+
return {'access': '', 'refresh': ''}
|
|
390
|
+
|
|
391
|
+
try:
|
|
392
|
+
# Generate tokens for the authenticated user
|
|
393
|
+
refresh = RefreshToken.for_user(user)
|
|
394
|
+
access_token = str(refresh.access_token)
|
|
395
|
+
refresh_token = str(refresh)
|
|
396
|
+
|
|
397
|
+
return {
|
|
398
|
+
'access': access_token,
|
|
399
|
+
'refresh': refresh_token
|
|
400
|
+
}
|
|
401
|
+
except ImportError:
|
|
402
|
+
return {'access': '', 'refresh': ''}
|
|
403
|
+
except Exception as e:
|
|
404
|
+
return {'access': '', 'refresh': ''}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: django-cfg
|
|
3
|
-
Version: 1.5.
|
|
3
|
+
Version: 1.5.2
|
|
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
|
|
@@ -34,7 +34,6 @@ Requires-Dist: click<9.0,>=8.2.0
|
|
|
34
34
|
Requires-Dist: cloudflare<5.0,>=4.3.0
|
|
35
35
|
Requires-Dist: colorlog<7.0,>=6.9.0
|
|
36
36
|
Requires-Dist: coolname<3.0,>=2.2.0
|
|
37
|
-
Requires-Dist: croniter<7.0,>=6.0.0
|
|
38
37
|
Requires-Dist: dj-database-url<4.0,>=3.0.0
|
|
39
38
|
Requires-Dist: django-admin-rangefilter<1.0,>=0.13.0
|
|
40
39
|
Requires-Dist: django-axes[ipware]<9.0.0,>=8.0.0
|
|
@@ -44,9 +43,9 @@ Requires-Dist: django-extensions<5.0,>=4.1.0
|
|
|
44
43
|
Requires-Dist: django-filter<26.0,>=25.0
|
|
45
44
|
Requires-Dist: django-import-export<5.0,>=4.3.0
|
|
46
45
|
Requires-Dist: django-json-widget<3.0,>=2.0.0
|
|
47
|
-
Requires-Dist: django-q2==1.8.0
|
|
48
46
|
Requires-Dist: django-ratelimit<5.0.0,>=4.1.0
|
|
49
47
|
Requires-Dist: django-redis<7.0,>=6.0.0
|
|
48
|
+
Requires-Dist: django-rq<4.0,>=3.1.0
|
|
50
49
|
Requires-Dist: django-tailwind[reload]<5.0.0,>=4.2.0
|
|
51
50
|
Requires-Dist: django-unfold<1.0,>=0.64.0
|
|
52
51
|
Requires-Dist: djangorestframework-simplejwt<6.0,>=5.5.0
|
|
@@ -73,10 +72,10 @@ Requires-Dist: pytelegrambotapi<5.0,>=4.28.0
|
|
|
73
72
|
Requires-Dist: python-json-logger<4.0,>=3.3.0
|
|
74
73
|
Requires-Dist: pyyaml<7.0,>=6.0
|
|
75
74
|
Requires-Dist: questionary<3.0,>=2.1.0
|
|
76
|
-
Requires-Dist: rearq<1.0,>=0.2.0
|
|
77
75
|
Requires-Dist: redis<7.0,>=6.4.0
|
|
78
76
|
Requires-Dist: requests<3.0,>=2.32.0
|
|
79
77
|
Requires-Dist: rich<15.0,>=14.0.0
|
|
78
|
+
Requires-Dist: rq-scheduler<1.0,>=0.14.0
|
|
80
79
|
Requires-Dist: sendgrid<7.0,>=6.12.0
|
|
81
80
|
Requires-Dist: setuptools>=75.0.0; python_version >= '3.13'
|
|
82
81
|
Requires-Dist: tenacity<10.0.0,>=9.1.2
|
|
@@ -130,7 +129,7 @@ Requires-Dist: mkdocs<2.0,>=1.6; extra == 'full'
|
|
|
130
129
|
Requires-Dist: mkdocstrings[python]<1.0,>=0.30; extra == 'full'
|
|
131
130
|
Requires-Dist: mypy<2.0,>=1.18; extra == 'full'
|
|
132
131
|
Requires-Dist: pre-commit<5.0,>=4.3; extra == 'full'
|
|
133
|
-
Requires-Dist: protobuf<
|
|
132
|
+
Requires-Dist: protobuf<7.0,>=5.0; extra == 'full'
|
|
134
133
|
Requires-Dist: pymdown-extensions<11.0,>=10.16; extra == 'full'
|
|
135
134
|
Requires-Dist: pytest-cov<8.0,>=7.0; extra == 'full'
|
|
136
135
|
Requires-Dist: pytest-django<5.0,>=4.11; extra == 'full'
|
|
@@ -146,7 +145,7 @@ Provides-Extra: grpc
|
|
|
146
145
|
Requires-Dist: djangogrpcframework>=0.2.1; extra == 'grpc'
|
|
147
146
|
Requires-Dist: grpcio-tools<2.0,>=1.50.0; extra == 'grpc'
|
|
148
147
|
Requires-Dist: grpcio<2.0,>=1.50.0; extra == 'grpc'
|
|
149
|
-
Requires-Dist: protobuf<
|
|
148
|
+
Requires-Dist: protobuf<7.0,>=5.0; extra == 'grpc'
|
|
150
149
|
Provides-Extra: local
|
|
151
150
|
Provides-Extra: tasks
|
|
152
151
|
Requires-Dist: redis<7.0,>=6.4.0; extra == 'tasks'
|