django-cfg 1.3.7__py3-none-any.whl → 1.3.11__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.
- django_cfg/__init__.py +1 -1
- django_cfg/apps/accounts/admin/__init__.py +24 -8
- django_cfg/apps/accounts/admin/activity_admin.py +146 -0
- django_cfg/apps/accounts/admin/filters.py +98 -22
- django_cfg/apps/accounts/admin/group_admin.py +86 -0
- django_cfg/apps/accounts/admin/inlines.py +42 -13
- django_cfg/apps/accounts/admin/otp_admin.py +115 -0
- django_cfg/apps/accounts/admin/registration_admin.py +173 -0
- django_cfg/apps/accounts/admin/resources.py +123 -19
- django_cfg/apps/accounts/admin/twilio_admin.py +327 -0
- django_cfg/apps/accounts/admin/user_admin.py +362 -0
- django_cfg/apps/agents/admin/__init__.py +17 -4
- django_cfg/apps/agents/admin/execution_admin.py +204 -183
- django_cfg/apps/agents/admin/registry_admin.py +230 -255
- django_cfg/apps/agents/admin/toolsets_admin.py +274 -321
- django_cfg/apps/agents/core/__init__.py +1 -1
- django_cfg/apps/agents/core/django_agent.py +221 -0
- django_cfg/apps/agents/core/exceptions.py +14 -0
- django_cfg/apps/agents/core/orchestrator.py +18 -3
- django_cfg/apps/knowbase/admin/__init__.py +1 -1
- django_cfg/apps/knowbase/admin/archive_admin.py +352 -640
- django_cfg/apps/knowbase/admin/chat_admin.py +258 -192
- django_cfg/apps/knowbase/admin/document_admin.py +269 -262
- django_cfg/apps/knowbase/admin/external_data_admin.py +271 -489
- django_cfg/apps/knowbase/config/settings.py +21 -4
- django_cfg/apps/knowbase/views/chat_views.py +3 -0
- django_cfg/apps/leads/admin/__init__.py +3 -1
- django_cfg/apps/leads/admin/leads_admin.py +235 -35
- django_cfg/apps/maintenance/admin/__init__.py +2 -2
- django_cfg/apps/maintenance/admin/api_key_admin.py +125 -63
- django_cfg/apps/maintenance/admin/log_admin.py +143 -61
- django_cfg/apps/maintenance/admin/scheduled_admin.py +212 -301
- django_cfg/apps/maintenance/admin/site_admin.py +213 -352
- django_cfg/apps/newsletter/admin/__init__.py +29 -2
- django_cfg/apps/newsletter/admin/newsletter_admin.py +531 -193
- django_cfg/apps/payments/admin/__init__.py +18 -27
- django_cfg/apps/payments/admin/api_keys_admin.py +179 -546
- django_cfg/apps/payments/admin/balance_admin.py +166 -632
- django_cfg/apps/payments/admin/currencies_admin.py +235 -607
- django_cfg/apps/payments/admin/endpoint_groups_admin.py +127 -0
- django_cfg/apps/payments/admin/filters.py +83 -3
- django_cfg/apps/payments/admin/networks_admin.py +269 -0
- django_cfg/apps/payments/admin/payments_admin.py +183 -460
- django_cfg/apps/payments/admin/subscriptions_admin.py +119 -636
- django_cfg/apps/payments/admin/tariffs_admin.py +248 -0
- django_cfg/apps/payments/admin_interface/serializers/payment_serializers.py +153 -34
- django_cfg/apps/payments/admin_interface/templates/payments/components/payment_card.html +121 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/payment_qr_code.html +95 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/progress_bar.html +37 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/provider_stats.html +60 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/status_badge.html +41 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/status_overview.html +83 -0
- django_cfg/apps/payments/admin_interface/templates/payments/payment_detail.html +363 -0
- django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +43 -17
- django_cfg/apps/payments/admin_interface/views/__init__.py +2 -0
- django_cfg/apps/payments/admin_interface/views/api/payments.py +102 -0
- django_cfg/apps/payments/admin_interface/views/api/webhook_admin.py +109 -63
- django_cfg/apps/payments/admin_interface/views/forms.py +5 -1
- django_cfg/apps/payments/config/__init__.py +14 -15
- django_cfg/apps/payments/config/django_cfg_integration.py +59 -1
- django_cfg/apps/payments/config/helpers.py +8 -13
- django_cfg/apps/payments/management/commands/manage_currencies.py +236 -274
- django_cfg/apps/payments/management/commands/manage_providers.py +4 -1
- django_cfg/apps/payments/middleware/api_access.py +32 -6
- django_cfg/apps/payments/migrations/0001_initial.py +33 -46
- django_cfg/apps/payments/migrations/0002_rename_payments_un_user_id_7f6e79_idx_payments_un_user_id_8ce187_idx_and_more.py +46 -0
- django_cfg/apps/payments/migrations/0003_universalpayment_status_changed_at.py +25 -0
- django_cfg/apps/payments/models/balance.py +12 -0
- django_cfg/apps/payments/models/currencies.py +106 -32
- django_cfg/apps/payments/models/managers/currency_managers.py +65 -0
- django_cfg/apps/payments/models/managers/payment_managers.py +142 -25
- django_cfg/apps/payments/models/payments.py +94 -0
- django_cfg/apps/payments/services/core/base.py +4 -4
- django_cfg/apps/payments/services/core/currency_service.py +35 -28
- django_cfg/apps/payments/services/core/payment_service.py +266 -39
- django_cfg/apps/payments/services/providers/__init__.py +3 -0
- django_cfg/apps/payments/services/providers/base.py +303 -41
- django_cfg/apps/payments/services/providers/models/__init__.py +42 -0
- django_cfg/apps/payments/services/providers/models/base.py +145 -0
- django_cfg/apps/payments/services/providers/models/providers.py +87 -0
- django_cfg/apps/payments/services/providers/models/universal.py +48 -0
- django_cfg/apps/payments/services/providers/nowpayments/__init__.py +31 -0
- django_cfg/apps/payments/services/providers/nowpayments/config.py +70 -0
- django_cfg/apps/payments/services/providers/nowpayments/models.py +150 -0
- django_cfg/apps/payments/services/providers/nowpayments/parsers.py +879 -0
- django_cfg/apps/payments/services/providers/nowpayments/provider.py +557 -0
- django_cfg/apps/payments/services/providers/nowpayments/sync.py +196 -0
- django_cfg/apps/payments/services/providers/registry.py +9 -37
- django_cfg/apps/payments/services/providers/sync_service.py +277 -0
- django_cfg/apps/payments/services/types/requests.py +19 -7
- django_cfg/apps/payments/signals/payment_signals.py +31 -2
- django_cfg/apps/payments/static/payments/js/api-client.js +29 -6
- django_cfg/apps/payments/static/payments/js/payment-detail.js +167 -0
- django_cfg/apps/payments/static/payments/js/payment-form.js +98 -32
- django_cfg/apps/payments/tasks/__init__.py +39 -0
- django_cfg/apps/payments/tasks/types.py +73 -0
- django_cfg/apps/payments/tasks/usage_tracking.py +308 -0
- django_cfg/apps/payments/templates/admin/payments/_components/dashboard_header.html +23 -0
- django_cfg/apps/payments/templates/admin/payments/_components/stats_card.html +25 -0
- django_cfg/apps/payments/templates/admin/payments/_components/stats_grid.html +16 -0
- django_cfg/apps/payments/templates/admin/payments/apikey/change_list.html +39 -0
- django_cfg/apps/payments/templates/admin/payments/balance/change_list.html +50 -0
- django_cfg/apps/payments/templates/admin/payments/currency/change_list.html +40 -0
- django_cfg/apps/payments/templates/admin/payments/payment/change_list.html +48 -0
- django_cfg/apps/payments/templates/admin/payments/subscription/change_list.html +48 -0
- django_cfg/apps/payments/templatetags/payment_tags.py +8 -0
- django_cfg/apps/payments/urls.py +3 -2
- django_cfg/apps/payments/urls_admin.py +1 -1
- django_cfg/apps/payments/views/api/currencies.py +8 -5
- django_cfg/apps/payments/views/overview/services.py +2 -2
- django_cfg/apps/payments/views/serializers/currencies.py +22 -8
- django_cfg/apps/support/admin/__init__.py +10 -1
- django_cfg/apps/support/admin/support_admin.py +338 -141
- django_cfg/apps/tasks/admin/__init__.py +11 -0
- django_cfg/apps/tasks/admin/tasks_admin.py +430 -0
- django_cfg/apps/tasks/static/tasks/css/dashboard.css +68 -217
- django_cfg/apps/tasks/static/tasks/js/api.js +40 -84
- django_cfg/apps/tasks/static/tasks/js/components/DataManager.js +24 -0
- django_cfg/apps/tasks/static/tasks/js/components/TabManager.js +85 -0
- django_cfg/apps/tasks/static/tasks/js/components/TaskRenderer.js +216 -0
- django_cfg/apps/tasks/static/tasks/js/dashboard/main.mjs +245 -0
- django_cfg/apps/tasks/static/tasks/js/dashboard/overview.mjs +123 -0
- django_cfg/apps/tasks/static/tasks/js/dashboard/queues.mjs +120 -0
- django_cfg/apps/tasks/static/tasks/js/dashboard/tasks.mjs +350 -0
- django_cfg/apps/tasks/static/tasks/js/dashboard/workers.mjs +169 -0
- django_cfg/apps/tasks/tasks/__init__.py +10 -0
- django_cfg/apps/tasks/tasks/demo_tasks.py +133 -0
- django_cfg/apps/tasks/templates/tasks/components/management_actions.html +42 -45
- django_cfg/apps/tasks/templates/tasks/components/{status_cards.html → overview_content.html} +30 -11
- django_cfg/apps/tasks/templates/tasks/components/queues_content.html +19 -0
- django_cfg/apps/tasks/templates/tasks/components/tab_navigation.html +16 -10
- django_cfg/apps/tasks/templates/tasks/components/tasks_content.html +51 -0
- django_cfg/apps/tasks/templates/tasks/components/workers_content.html +30 -0
- django_cfg/apps/tasks/templates/tasks/layout/base.html +117 -0
- django_cfg/apps/tasks/templates/tasks/pages/dashboard.html +82 -0
- django_cfg/apps/tasks/templates/tasks/partials/task_row_template.html +40 -0
- django_cfg/apps/tasks/templates/tasks/widgets/task_filters.html +37 -0
- django_cfg/apps/tasks/templates/tasks/widgets/task_footer.html +41 -0
- django_cfg/apps/tasks/templates/tasks/widgets/task_table.html +50 -0
- django_cfg/apps/tasks/urls.py +2 -2
- django_cfg/apps/tasks/urls_admin.py +2 -2
- django_cfg/apps/tasks/utils/__init__.py +1 -0
- django_cfg/apps/tasks/utils/simulator.py +356 -0
- django_cfg/apps/tasks/views/__init__.py +16 -0
- django_cfg/apps/tasks/views/api.py +569 -0
- django_cfg/apps/tasks/views/dashboard.py +58 -0
- django_cfg/config.py +1 -1
- django_cfg/core/config.py +10 -5
- django_cfg/core/generation.py +1 -1
- django_cfg/core/integration/__init__.py +21 -0
- django_cfg/management/commands/__init__.py +13 -1
- django_cfg/management/commands/migrate_all.py +9 -3
- django_cfg/management/commands/migrator.py +11 -6
- django_cfg/management/commands/rundramatiq.py +3 -2
- django_cfg/management/commands/rundramatiq_simulator.py +430 -0
- django_cfg/middleware/__init__.py +0 -2
- django_cfg/models/api_keys.py +115 -0
- django_cfg/models/constance.py +0 -11
- django_cfg/models/payments.py +137 -3
- django_cfg/modules/django_admin/__init__.py +64 -0
- django_cfg/modules/django_admin/decorators/__init__.py +13 -0
- django_cfg/modules/django_admin/decorators/actions.py +106 -0
- django_cfg/modules/django_admin/decorators/display.py +106 -0
- django_cfg/modules/django_admin/mixins/__init__.py +14 -0
- django_cfg/modules/django_admin/mixins/display_mixin.py +81 -0
- django_cfg/modules/django_admin/mixins/optimization_mixin.py +41 -0
- django_cfg/modules/django_admin/mixins/standalone_actions_mixin.py +202 -0
- django_cfg/modules/django_admin/models/__init__.py +20 -0
- django_cfg/modules/django_admin/models/action_models.py +33 -0
- django_cfg/modules/django_admin/models/badge_models.py +20 -0
- django_cfg/modules/django_admin/models/base.py +26 -0
- django_cfg/modules/django_admin/models/display_models.py +31 -0
- django_cfg/modules/django_admin/utils/badges.py +159 -0
- django_cfg/modules/django_admin/utils/displays.py +247 -0
- django_cfg/modules/django_currency/__init__.py +2 -2
- django_cfg/modules/django_currency/clients/__init__.py +2 -2
- django_cfg/modules/django_currency/clients/hybrid_client.py +587 -0
- django_cfg/modules/django_currency/core/converter.py +12 -12
- django_cfg/modules/django_currency/database/__init__.py +2 -2
- django_cfg/modules/django_currency/database/database_loader.py +93 -42
- django_cfg/modules/django_llm/llm/client.py +10 -2
- django_cfg/modules/django_tasks.py +54 -21
- django_cfg/modules/django_unfold/callbacks/actions.py +1 -1
- django_cfg/modules/django_unfold/callbacks/statistics.py +1 -1
- django_cfg/modules/django_unfold/dashboard.py +14 -13
- django_cfg/modules/django_unfold/models/config.py +1 -1
- django_cfg/registry/core.py +7 -9
- django_cfg/registry/third_party.py +2 -2
- django_cfg/template_archive/django_sample.zip +0 -0
- {django_cfg-1.3.7.dist-info → django_cfg-1.3.11.dist-info}/METADATA +2 -1
- {django_cfg-1.3.7.dist-info → django_cfg-1.3.11.dist-info}/RECORD +198 -160
- django_cfg/apps/accounts/admin/activity.py +0 -96
- django_cfg/apps/accounts/admin/group.py +0 -17
- django_cfg/apps/accounts/admin/otp.py +0 -59
- django_cfg/apps/accounts/admin/registration_source.py +0 -97
- django_cfg/apps/accounts/admin/twilio_response.py +0 -227
- django_cfg/apps/accounts/admin/user.py +0 -300
- django_cfg/apps/agents/core/agent.py +0 -281
- django_cfg/apps/payments/admin_interface/old/payments/base.html +0 -175
- django_cfg/apps/payments/admin_interface/old/payments/components/dev_tool_card.html +0 -125
- django_cfg/apps/payments/admin_interface/old/payments/components/loading_spinner.html +0 -16
- django_cfg/apps/payments/admin_interface/old/payments/components/ngrok_status_card.html +0 -113
- django_cfg/apps/payments/admin_interface/old/payments/components/notification.html +0 -27
- django_cfg/apps/payments/admin_interface/old/payments/components/provider_card.html +0 -86
- django_cfg/apps/payments/admin_interface/old/payments/components/status_card.html +0 -35
- django_cfg/apps/payments/admin_interface/old/payments/currency_converter.html +0 -382
- django_cfg/apps/payments/admin_interface/old/payments/payment_dashboard.html +0 -309
- django_cfg/apps/payments/admin_interface/old/payments/payment_form.html +0 -303
- django_cfg/apps/payments/admin_interface/old/payments/payment_list.html +0 -382
- django_cfg/apps/payments/admin_interface/old/payments/payment_status.html +0 -500
- django_cfg/apps/payments/admin_interface/old/payments/webhook_dashboard.html +0 -518
- django_cfg/apps/payments/admin_interface/old/static/payments/css/components.css +0 -619
- django_cfg/apps/payments/admin_interface/old/static/payments/css/dashboard.css +0 -188
- django_cfg/apps/payments/admin_interface/old/static/payments/js/components.js +0 -545
- django_cfg/apps/payments/admin_interface/old/static/payments/js/ngrok-status.js +0 -163
- django_cfg/apps/payments/admin_interface/old/static/payments/js/utils.js +0 -412
- django_cfg/apps/payments/config/constance/__init__.py +0 -22
- django_cfg/apps/payments/config/constance/config_service.py +0 -123
- django_cfg/apps/payments/config/constance/fields.py +0 -69
- django_cfg/apps/payments/config/constance/settings.py +0 -160
- django_cfg/apps/payments/services/providers/nowpayments.py +0 -478
- django_cfg/apps/tasks/admin.py +0 -320
- django_cfg/apps/tasks/static/tasks/js/dashboard.js +0 -614
- django_cfg/apps/tasks/static/tasks/js/modals.js +0 -452
- django_cfg/apps/tasks/static/tasks/js/notifications.js +0 -144
- django_cfg/apps/tasks/static/tasks/js/task-monitor.js +0 -454
- django_cfg/apps/tasks/static/tasks/js/theme.js +0 -77
- django_cfg/apps/tasks/templates/tasks/base.html +0 -96
- django_cfg/apps/tasks/templates/tasks/components/info_cards.html +0 -85
- django_cfg/apps/tasks/templates/tasks/components/overview_tab.html +0 -22
- django_cfg/apps/tasks/templates/tasks/components/queues_tab.html +0 -19
- django_cfg/apps/tasks/templates/tasks/components/task_details_modal.html +0 -103
- django_cfg/apps/tasks/templates/tasks/components/tasks_tab.html +0 -32
- django_cfg/apps/tasks/templates/tasks/components/workers_tab.html +0 -29
- django_cfg/apps/tasks/templates/tasks/dashboard.html +0 -29
- django_cfg/apps/tasks/views.py +0 -461
- django_cfg/management/commands/auto_generate.py +0 -486
- django_cfg/middleware/static_nocache.py +0 -55
- django_cfg/modules/django_currency/clients/yahoo_client.py +0 -157
- /django_cfg/modules/{django_unfold → django_admin}/icons/README.md +0 -0
- /django_cfg/modules/{django_unfold → django_admin}/icons/__init__.py +0 -0
- /django_cfg/modules/{django_unfold → django_admin}/icons/constants.py +0 -0
- /django_cfg/modules/{django_unfold → django_admin}/icons/generate_icons.py +0 -0
- {django_cfg-1.3.7.dist-info → django_cfg-1.3.11.dist-info}/WHEEL +0 -0
- {django_cfg-1.3.7.dist-info → django_cfg-1.3.11.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.3.7.dist-info → django_cfg-1.3.11.dist-info}/licenses/LICENSE +0 -0
@@ -4,21 +4,41 @@ Django CFG Integration Package.
|
|
4
4
|
Provides URL integration and startup information display.
|
5
5
|
"""
|
6
6
|
|
7
|
+
import os
|
8
|
+
|
7
9
|
from .url_integration import add_django_cfg_urls, get_django_cfg_urls_info
|
8
10
|
from .display.startup import StartupDisplayManager
|
9
11
|
from .display.ngrok import NgrokDisplayManager
|
10
12
|
|
13
|
+
# Module-level flag that persists across hot reloads
|
14
|
+
_startup_info_shown = False
|
15
|
+
|
16
|
+
|
11
17
|
def print_startup_info():
|
12
18
|
"""Print startup information based on config.startup_info_mode."""
|
19
|
+
# Skip startup info in development mode to avoid hot reload spam
|
20
|
+
try:
|
21
|
+
from django.conf import settings
|
22
|
+
if settings.DEBUG:
|
23
|
+
return
|
24
|
+
except Exception:
|
25
|
+
pass
|
26
|
+
|
13
27
|
try:
|
14
28
|
manager = StartupDisplayManager()
|
15
29
|
manager.display_startup_info()
|
30
|
+
|
16
31
|
except Exception as e:
|
17
32
|
import traceback
|
18
33
|
print(f"❌ ERROR in print_startup_info: {e}")
|
19
34
|
print("🔍 TRACEBACK:")
|
20
35
|
traceback.print_exc()
|
21
36
|
|
37
|
+
def reset_startup_info_flag():
|
38
|
+
"""Reset the startup info display flag. Useful for testing or manual reset."""
|
39
|
+
global _startup_info_shown
|
40
|
+
_startup_info_shown = False
|
41
|
+
|
22
42
|
def print_ngrok_tunnel_info(tunnel_url: str):
|
23
43
|
"""Print ngrok tunnel information after tunnel is established."""
|
24
44
|
try:
|
@@ -37,6 +57,7 @@ __all__ = [
|
|
37
57
|
"add_django_cfg_urls",
|
38
58
|
"get_django_cfg_urls_info",
|
39
59
|
"print_startup_info",
|
60
|
+
"reset_startup_info_flag",
|
40
61
|
"print_ngrok_tunnel_info",
|
41
62
|
"get_version_info",
|
42
63
|
"get_latest_version",
|
@@ -1 +1,13 @@
|
|
1
|
-
|
1
|
+
"""
|
2
|
+
Django management commands for django-cfg.
|
3
|
+
|
4
|
+
This package contains all Django management commands including:
|
5
|
+
|
6
|
+
Django App Agent Commands:
|
7
|
+
- app_agent_generate: Generate Django applications with AI assistance
|
8
|
+
- app_agent_diagnose: Diagnose problems in Django/Django-cfg projects
|
9
|
+
- app_agent_info: Show information about Django App Agent capabilities
|
10
|
+
|
11
|
+
Other Commands:
|
12
|
+
- Various utility and maintenance commands
|
13
|
+
"""
|
@@ -73,7 +73,9 @@ class Command(BaseCommand):
|
|
73
73
|
try:
|
74
74
|
call_command("migrate", app_label, database=db_name, verbosity=1)
|
75
75
|
except Exception as e:
|
76
|
-
self.stdout.write(self.style.
|
76
|
+
self.stdout.write(self.style.ERROR(f" ❌ Migration failed for {app_label} on {db_name}: {e}"))
|
77
|
+
logger.error(f"Migration failed for {app_label} on {db_name}: {e}")
|
78
|
+
raise SystemExit(1)
|
77
79
|
else:
|
78
80
|
self.stdout.write(f" Would run: migrate {app_label} --database={db_name}")
|
79
81
|
else:
|
@@ -83,7 +85,9 @@ class Command(BaseCommand):
|
|
83
85
|
try:
|
84
86
|
call_command("migrate", database=db_name, verbosity=1)
|
85
87
|
except Exception as e:
|
86
|
-
self.stdout.write(self.style.
|
88
|
+
self.stdout.write(self.style.ERROR(f" ❌ Migration failed for all apps on {db_name}: {e}"))
|
89
|
+
logger.error(f"Migration failed for all apps on {db_name}: {e}")
|
90
|
+
raise SystemExit(1)
|
87
91
|
else:
|
88
92
|
self.stdout.write(f" Would run: migrate --database={db_name}")
|
89
93
|
|
@@ -93,7 +97,9 @@ class Command(BaseCommand):
|
|
93
97
|
try:
|
94
98
|
call_command("migrate", "constance", database="default", verbosity=1)
|
95
99
|
except Exception as e:
|
96
|
-
self.stdout.write(self.style.
|
100
|
+
self.stdout.write(self.style.ERROR(f"❌ Constance migration failed: {e}"))
|
101
|
+
logger.error(f"Constance migration failed: {e}")
|
102
|
+
raise SystemExit(1)
|
97
103
|
else:
|
98
104
|
self.stdout.write(" Would run: migrate constance --database=default")
|
99
105
|
|
@@ -125,6 +125,11 @@ class Command(BaseCommand):
|
|
125
125
|
except Exception as e:
|
126
126
|
self.stdout.write(self.style.WARNING(f"⚠️ Warning creating migrations: {e}"))
|
127
127
|
|
128
|
+
def _raise_system_exit(self, message):
|
129
|
+
self.stdout.write(self.style.ERROR(f"❌ {message}"))
|
130
|
+
logger.error(message)
|
131
|
+
# raise SystemExit(1)
|
132
|
+
|
128
133
|
def migrate_database(self, db_name):
|
129
134
|
"""Migrate specific database"""
|
130
135
|
try:
|
@@ -154,12 +159,12 @@ class Command(BaseCommand):
|
|
154
159
|
self.stdout.write(f" 📦 Migrating {app}...")
|
155
160
|
call_command("migrate", app, database=db_name, verbosity=1)
|
156
161
|
except Exception as e:
|
157
|
-
self.
|
162
|
+
self._raise_system_exit(f"Migration failed for {app} on {db_name}: {e}")
|
158
163
|
|
159
164
|
self.stdout.write(self.style.SUCCESS(f"✅ {db_name} migration completed!"))
|
160
165
|
|
161
166
|
except Exception as e:
|
162
|
-
self.
|
167
|
+
self._raise_system_exit(f"Error migrating {db_name}: {e}")
|
163
168
|
|
164
169
|
def migrate_constance_if_needed(self):
|
165
170
|
"""Always migrate constance app if it's installed"""
|
@@ -173,12 +178,12 @@ class Command(BaseCommand):
|
|
173
178
|
call_command("migrate", "constance", database="default", verbosity=1)
|
174
179
|
self.stdout.write(self.style.SUCCESS("✅ Constance migration completed!"))
|
175
180
|
except Exception as e:
|
176
|
-
self.
|
181
|
+
self._raise_system_exit(f"Constance migration failed: {e}")
|
177
182
|
else:
|
178
183
|
self.stdout.write(self.style.WARNING("⚠️ Constance not found in INSTALLED_APPS"))
|
179
184
|
|
180
185
|
except Exception as e:
|
181
|
-
self.
|
186
|
+
self._raise_system_exit(f"Could not migrate constance: {e}")
|
182
187
|
|
183
188
|
def migrate_app(self, app_name):
|
184
189
|
"""Migrate specific app across all databases"""
|
@@ -192,7 +197,7 @@ class Command(BaseCommand):
|
|
192
197
|
try:
|
193
198
|
call_command("migrate", app_name, database=db_name, verbosity=1)
|
194
199
|
except Exception as e:
|
195
|
-
self.
|
200
|
+
self._raise_system_exit(f"Migration failed for {app_name} on {db_name}: {e}")
|
196
201
|
|
197
202
|
def show_database_status(self):
|
198
203
|
"""Show status of all databases and their apps"""
|
@@ -265,7 +270,7 @@ class Command(BaseCommand):
|
|
265
270
|
self.stdout.write(f"📦 Installed Apps: {len(settings.INSTALLED_APPS)}")
|
266
271
|
|
267
272
|
except Exception as e:
|
268
|
-
self.
|
273
|
+
self._raise_system_exit(f"Error getting Django config info: {e}")
|
269
274
|
|
270
275
|
def get_apps_for_database(self, db_name: str):
|
271
276
|
"""Get apps for specific database with smart logic for default"""
|
@@ -16,8 +16,6 @@ from django.conf import settings
|
|
16
16
|
from django.core.management.base import BaseCommand
|
17
17
|
from django.utils.module_loading import module_has_submodule
|
18
18
|
from django_cfg.modules.django_logger import get_logger
|
19
|
-
|
20
|
-
|
21
19
|
from django_cfg.modules.django_tasks import get_task_service
|
22
20
|
|
23
21
|
|
@@ -111,8 +109,10 @@ class Command(BaseCommand):
|
|
111
109
|
# If dry run, show command and exit
|
112
110
|
if dry_run:
|
113
111
|
executable_name = "dramatiq"
|
112
|
+
|
114
113
|
process_args = [
|
115
114
|
executable_name,
|
115
|
+
"django_cfg.modules.dramatiq_setup", # Broker module
|
116
116
|
"--processes", str(processes),
|
117
117
|
"--threads", str(threads),
|
118
118
|
"--worker-shutdown-timeout", str(worker_shutdown_timeout),
|
@@ -150,6 +150,7 @@ class Command(BaseCommand):
|
|
150
150
|
# Build process arguments exactly like django_dramatiq
|
151
151
|
process_args = [
|
152
152
|
executable_name,
|
153
|
+
"django_cfg.modules.dramatiq_setup", # Broker module
|
153
154
|
"--processes", str(processes),
|
154
155
|
"--threads", str(threads),
|
155
156
|
"--worker-shutdown-timeout", str(worker_shutdown_timeout),
|
@@ -0,0 +1,430 @@
|
|
1
|
+
"""
|
2
|
+
Django management command for simulating Dramatiq tasks and workers.
|
3
|
+
|
4
|
+
Usage:
|
5
|
+
python manage.py rundramatiq_simulator --help
|
6
|
+
python manage.py rundramatiq_simulator --workers 5
|
7
|
+
python manage.py rundramatiq_simulator --clear-only
|
8
|
+
python manage.py rundramatiq_simulator --show-keys
|
9
|
+
"""
|
10
|
+
|
11
|
+
import redis
|
12
|
+
import json
|
13
|
+
import time
|
14
|
+
from datetime import datetime, timezone
|
15
|
+
import random
|
16
|
+
from typing import Dict, Any, Optional
|
17
|
+
|
18
|
+
from django.core.management.base import BaseCommand, CommandError
|
19
|
+
from django.conf import settings
|
20
|
+
|
21
|
+
from django_cfg.modules.django_tasks import DjangoTasks
|
22
|
+
|
23
|
+
|
24
|
+
class TaskSimulator:
|
25
|
+
"""Task data simulator for Tasks Dashboard."""
|
26
|
+
|
27
|
+
def __init__(self):
|
28
|
+
"""Initialize the simulator."""
|
29
|
+
self.tasks_service = DjangoTasks()
|
30
|
+
|
31
|
+
# Get Redis client using the same logic as DjangoTasks
|
32
|
+
try:
|
33
|
+
redis_url = self.tasks_service.get_redis_url()
|
34
|
+
if not redis_url:
|
35
|
+
raise RuntimeError("No Redis URL available")
|
36
|
+
|
37
|
+
# Parse URL for connection
|
38
|
+
from urllib.parse import urlparse
|
39
|
+
parsed = urlparse(redis_url)
|
40
|
+
|
41
|
+
self.redis_client = redis.Redis(
|
42
|
+
host=parsed.hostname or 'localhost',
|
43
|
+
port=parsed.port or 6379,
|
44
|
+
db=int(parsed.path.lstrip('/')) if parsed.path else 1,
|
45
|
+
decode_responses=True
|
46
|
+
)
|
47
|
+
|
48
|
+
except Exception as e:
|
49
|
+
raise CommandError(f"Failed to connect to Redis: {e}")
|
50
|
+
|
51
|
+
# Get queue configuration
|
52
|
+
try:
|
53
|
+
config = self.tasks_service.get_config()
|
54
|
+
self.queues = config.tasks.dramatiq.queues
|
55
|
+
except Exception as e:
|
56
|
+
# Use default queues if we can't get configuration
|
57
|
+
self.queues = ['critical', 'high', 'default', 'low', 'background', 'payments', 'agents']
|
58
|
+
|
59
|
+
def clear_all_data(self) -> int:
|
60
|
+
"""
|
61
|
+
Clear all test data.
|
62
|
+
|
63
|
+
Returns:
|
64
|
+
Number of deleted keys
|
65
|
+
"""
|
66
|
+
keys = self.redis_client.keys("dramatiq:*")
|
67
|
+
if keys:
|
68
|
+
deleted = self.redis_client.delete(*keys)
|
69
|
+
return deleted
|
70
|
+
return 0
|
71
|
+
|
72
|
+
def simulate_queues(self, pending_tasks_per_queue=None, failed_tasks_per_queue=None) -> Dict[str, Dict[str, int]]:
|
73
|
+
"""
|
74
|
+
Simulate queues with tasks.
|
75
|
+
|
76
|
+
Args:
|
77
|
+
pending_tasks_per_queue: Dict[str, int] - number of pending tasks per queue
|
78
|
+
failed_tasks_per_queue: Dict[str, int] - number of failed tasks per queue
|
79
|
+
|
80
|
+
Returns:
|
81
|
+
Dict with information about created tasks
|
82
|
+
"""
|
83
|
+
if pending_tasks_per_queue is None:
|
84
|
+
pending_tasks_per_queue = {
|
85
|
+
'critical': 2,
|
86
|
+
'high': 5,
|
87
|
+
'default': 12,
|
88
|
+
'low': 8,
|
89
|
+
'background': 15,
|
90
|
+
'payments': 3,
|
91
|
+
'agents': 7
|
92
|
+
}
|
93
|
+
|
94
|
+
if failed_tasks_per_queue is None:
|
95
|
+
failed_tasks_per_queue = {
|
96
|
+
'critical': 0,
|
97
|
+
'high': 1,
|
98
|
+
'default': 3,
|
99
|
+
'low': 2,
|
100
|
+
'background': 1,
|
101
|
+
'payments': 0,
|
102
|
+
'agents': 1
|
103
|
+
}
|
104
|
+
|
105
|
+
results = {}
|
106
|
+
|
107
|
+
for queue_name in self.queues:
|
108
|
+
queue_results = {'pending': 0, 'failed': 0}
|
109
|
+
|
110
|
+
# Pending tasks
|
111
|
+
pending_count = pending_tasks_per_queue.get(queue_name, 0)
|
112
|
+
if pending_count > 0:
|
113
|
+
queue_key = f"dramatiq:default.DQ.{queue_name}"
|
114
|
+
|
115
|
+
# Add fake tasks to queue
|
116
|
+
for i in range(pending_count):
|
117
|
+
task_data = {
|
118
|
+
"queue_name": queue_name,
|
119
|
+
"actor_name": f"process_{queue_name}_task",
|
120
|
+
"args": [f"task_{i}"],
|
121
|
+
"kwargs": {},
|
122
|
+
"options": {},
|
123
|
+
"message_id": f"msg_{queue_name}_{i}_{int(time.time())}",
|
124
|
+
"message_timestamp": int(time.time() * 1000)
|
125
|
+
}
|
126
|
+
self.redis_client.lpush(queue_key, json.dumps(task_data))
|
127
|
+
|
128
|
+
queue_results['pending'] = pending_count
|
129
|
+
|
130
|
+
# Failed tasks
|
131
|
+
failed_count = failed_tasks_per_queue.get(queue_name, 0)
|
132
|
+
if failed_count > 0:
|
133
|
+
failed_key = f"dramatiq:default.DQ.{queue_name}.failed"
|
134
|
+
|
135
|
+
# Add fake failed tasks
|
136
|
+
for i in range(failed_count):
|
137
|
+
failed_task_data = {
|
138
|
+
"queue_name": queue_name,
|
139
|
+
"actor_name": f"failed_{queue_name}_task",
|
140
|
+
"args": [f"failed_task_{i}"],
|
141
|
+
"kwargs": {},
|
142
|
+
"options": {},
|
143
|
+
"message_id": f"failed_msg_{queue_name}_{i}_{int(time.time())}",
|
144
|
+
"message_timestamp": int(time.time() * 1000),
|
145
|
+
"error": f"Simulated error for {queue_name} task {i}"
|
146
|
+
}
|
147
|
+
self.redis_client.lpush(failed_key, json.dumps(failed_task_data))
|
148
|
+
|
149
|
+
queue_results['failed'] = failed_count
|
150
|
+
|
151
|
+
if queue_results['pending'] > 0 or queue_results['failed'] > 0:
|
152
|
+
results[queue_name] = queue_results
|
153
|
+
|
154
|
+
return results
|
155
|
+
|
156
|
+
def simulate_workers(self, worker_count=3) -> list:
|
157
|
+
"""
|
158
|
+
Симулировать активных воркеров.
|
159
|
+
|
160
|
+
Args:
|
161
|
+
worker_count: Количество воркеров для симуляции
|
162
|
+
|
163
|
+
Returns:
|
164
|
+
Список ID созданных воркеров
|
165
|
+
"""
|
166
|
+
worker_ids = []
|
167
|
+
|
168
|
+
for i in range(worker_count):
|
169
|
+
worker_id = f"worker_{i}_{int(time.time())}"
|
170
|
+
worker_key = f"dramatiq:worker:{worker_id}"
|
171
|
+
|
172
|
+
worker_data = {
|
173
|
+
"worker_id": worker_id,
|
174
|
+
"hostname": f"localhost",
|
175
|
+
"pid": 1000 + i,
|
176
|
+
"queues": self.queues,
|
177
|
+
"started_at": datetime.now(timezone.utc).isoformat(),
|
178
|
+
"last_heartbeat": datetime.now(timezone.utc).isoformat(),
|
179
|
+
"status": "active"
|
180
|
+
}
|
181
|
+
|
182
|
+
# Устанавливаем данные воркера с TTL
|
183
|
+
self.redis_client.setex(
|
184
|
+
worker_key,
|
185
|
+
300, # 5 минут TTL
|
186
|
+
json.dumps(worker_data)
|
187
|
+
)
|
188
|
+
|
189
|
+
worker_ids.append(worker_id)
|
190
|
+
|
191
|
+
return worker_ids
|
192
|
+
|
193
|
+
def simulate_task_statistics(self) -> Dict[str, Any]:
|
194
|
+
"""
|
195
|
+
Симулировать статистику задач.
|
196
|
+
|
197
|
+
Returns:
|
198
|
+
Созданная статистика
|
199
|
+
"""
|
200
|
+
stats_data = {
|
201
|
+
"total_processed": random.randint(1000, 2000),
|
202
|
+
"total_failed": random.randint(30, 80),
|
203
|
+
"total_retried": random.randint(15, 40),
|
204
|
+
"processing_time_avg": round(random.uniform(1.5, 4.0), 2),
|
205
|
+
"last_updated": datetime.now(timezone.utc).isoformat()
|
206
|
+
}
|
207
|
+
|
208
|
+
stats_key = "dramatiq:stats"
|
209
|
+
self.redis_client.setex(stats_key, 3600, json.dumps(stats_data))
|
210
|
+
|
211
|
+
return stats_data
|
212
|
+
|
213
|
+
def run_simulation(self, workers=3, clear_first=True) -> Dict[str, Any]:
|
214
|
+
"""
|
215
|
+
Запустить полную симуляцию.
|
216
|
+
|
217
|
+
Args:
|
218
|
+
workers: Количество воркеров
|
219
|
+
clear_first: Очистить данные перед симуляцией
|
220
|
+
|
221
|
+
Returns:
|
222
|
+
Результаты симуляции
|
223
|
+
"""
|
224
|
+
results = {
|
225
|
+
'cleared_keys': 0,
|
226
|
+
'queues': {},
|
227
|
+
'workers': [],
|
228
|
+
'statistics': {}
|
229
|
+
}
|
230
|
+
|
231
|
+
if clear_first:
|
232
|
+
results['cleared_keys'] = self.clear_all_data()
|
233
|
+
|
234
|
+
results['queues'] = self.simulate_queues()
|
235
|
+
results['workers'] = self.simulate_workers(workers)
|
236
|
+
results['statistics'] = self.simulate_task_statistics()
|
237
|
+
|
238
|
+
return results
|
239
|
+
|
240
|
+
def get_redis_summary(self) -> Dict[str, Any]:
|
241
|
+
"""Получить сводку по данным в Redis."""
|
242
|
+
summary = {
|
243
|
+
'total_keys': 0,
|
244
|
+
'queues': {},
|
245
|
+
'workers': 0,
|
246
|
+
'statistics': None
|
247
|
+
}
|
248
|
+
|
249
|
+
# Подсчитываем все ключи
|
250
|
+
all_keys = self.redis_client.keys("dramatiq:*")
|
251
|
+
summary['total_keys'] = len(all_keys)
|
252
|
+
|
253
|
+
# Анализируем очереди
|
254
|
+
for queue_name in self.queues:
|
255
|
+
pending_key = f"dramatiq:default.DQ.{queue_name}"
|
256
|
+
failed_key = f"dramatiq:default.DQ.{queue_name}.failed"
|
257
|
+
|
258
|
+
pending = self.redis_client.llen(pending_key)
|
259
|
+
failed = self.redis_client.llen(failed_key)
|
260
|
+
|
261
|
+
if pending > 0 or failed > 0:
|
262
|
+
summary['queues'][queue_name] = {
|
263
|
+
'pending': pending,
|
264
|
+
'failed': failed
|
265
|
+
}
|
266
|
+
|
267
|
+
# Подсчитываем воркеров
|
268
|
+
worker_keys = self.redis_client.keys("dramatiq:worker:*")
|
269
|
+
summary['workers'] = len(worker_keys)
|
270
|
+
|
271
|
+
# Получаем статистику
|
272
|
+
stats_key = "dramatiq:stats"
|
273
|
+
if self.redis_client.exists(stats_key):
|
274
|
+
try:
|
275
|
+
stats_data = self.redis_client.get(stats_key)
|
276
|
+
summary['statistics'] = json.loads(stats_data)
|
277
|
+
except:
|
278
|
+
pass
|
279
|
+
|
280
|
+
return summary
|
281
|
+
|
282
|
+
|
283
|
+
class Command(BaseCommand):
|
284
|
+
"""Django management command для симуляции Dramatiq данных."""
|
285
|
+
|
286
|
+
help = 'Simulate Dramatiq tasks and workers for dashboard testing'
|
287
|
+
|
288
|
+
def add_arguments(self, parser):
|
289
|
+
"""Добавить аргументы команды."""
|
290
|
+
parser.add_argument(
|
291
|
+
'--workers',
|
292
|
+
type=int,
|
293
|
+
default=3,
|
294
|
+
help='Number of workers to simulate (default: 3)'
|
295
|
+
)
|
296
|
+
|
297
|
+
parser.add_argument(
|
298
|
+
'--no-clear',
|
299
|
+
action='store_true',
|
300
|
+
help='Do not clear existing data before simulation'
|
301
|
+
)
|
302
|
+
|
303
|
+
parser.add_argument(
|
304
|
+
'--clear-only',
|
305
|
+
action='store_true',
|
306
|
+
help='Only clear data, do not simulate'
|
307
|
+
)
|
308
|
+
|
309
|
+
parser.add_argument(
|
310
|
+
'--show-keys',
|
311
|
+
action='store_true',
|
312
|
+
help='Show Redis keys after operation'
|
313
|
+
)
|
314
|
+
|
315
|
+
parser.add_argument(
|
316
|
+
'--summary',
|
317
|
+
action='store_true',
|
318
|
+
help='Show summary of current Redis data'
|
319
|
+
)
|
320
|
+
|
321
|
+
def handle(self, *args, **options):
|
322
|
+
"""Выполнить команду."""
|
323
|
+
try:
|
324
|
+
simulator = TaskSimulator()
|
325
|
+
|
326
|
+
# Показать только сводку
|
327
|
+
if options['summary']:
|
328
|
+
self.show_summary(simulator)
|
329
|
+
return
|
330
|
+
|
331
|
+
# Только очистка
|
332
|
+
if options['clear_only']:
|
333
|
+
self.stdout.write("🧹 Clearing all test data...")
|
334
|
+
cleared = simulator.clear_all_data()
|
335
|
+
self.stdout.write(
|
336
|
+
self.style.SUCCESS(f"✅ Cleared {cleared} Redis keys")
|
337
|
+
)
|
338
|
+
return
|
339
|
+
|
340
|
+
# Полная симуляция
|
341
|
+
self.stdout.write("🎭 Starting Dramatiq Task Simulation")
|
342
|
+
self.stdout.write("=" * 50)
|
343
|
+
|
344
|
+
results = simulator.run_simulation(
|
345
|
+
workers=options['workers'],
|
346
|
+
clear_first=not options['no_clear']
|
347
|
+
)
|
348
|
+
|
349
|
+
# Показываем результаты
|
350
|
+
if results['cleared_keys'] > 0:
|
351
|
+
self.stdout.write(f"🧹 Cleared {results['cleared_keys']} existing keys")
|
352
|
+
|
353
|
+
self.stdout.write(f"📋 Created queues:")
|
354
|
+
total_pending = 0
|
355
|
+
total_failed = 0
|
356
|
+
|
357
|
+
for queue_name, counts in results['queues'].items():
|
358
|
+
pending = counts['pending']
|
359
|
+
failed = counts['failed']
|
360
|
+
total_pending += pending
|
361
|
+
total_failed += failed
|
362
|
+
|
363
|
+
self.stdout.write(f" {queue_name}: {pending} pending, {failed} failed")
|
364
|
+
|
365
|
+
self.stdout.write(f"👷 Created {len(results['workers'])} workers")
|
366
|
+
self.stdout.write(f"📊 Added task statistics")
|
367
|
+
|
368
|
+
self.stdout.write("=" * 50)
|
369
|
+
self.stdout.write(self.style.SUCCESS("✅ Simulation completed!"))
|
370
|
+
|
371
|
+
self.stdout.write(f"\n📊 Summary:")
|
372
|
+
active_queues = len(results['queues'])
|
373
|
+
self.stdout.write(f" Active Queues: {active_queues}")
|
374
|
+
self.stdout.write(f" Active Workers: {len(results['workers'])}")
|
375
|
+
self.stdout.write(f" Pending Tasks: {total_pending}")
|
376
|
+
self.stdout.write(f" Failed Tasks: {total_failed}")
|
377
|
+
|
378
|
+
self.stdout.write(f"\n🌐 Dashboard URL: http://localhost:8000/cfg/admin/django_cfg_tasks/admin/dashboard/")
|
379
|
+
|
380
|
+
# Показать ключи если запрошено
|
381
|
+
if options['show_keys']:
|
382
|
+
self.show_redis_keys(simulator)
|
383
|
+
|
384
|
+
except Exception as e:
|
385
|
+
raise CommandError(f"Simulation failed: {e}")
|
386
|
+
|
387
|
+
def show_summary(self, simulator: TaskSimulator):
|
388
|
+
"""Показать сводку текущих данных."""
|
389
|
+
self.stdout.write("📊 Current Redis Data Summary")
|
390
|
+
self.stdout.write("=" * 40)
|
391
|
+
|
392
|
+
summary = simulator.get_redis_summary()
|
393
|
+
|
394
|
+
self.stdout.write(f"Total Redis keys: {summary['total_keys']}")
|
395
|
+
self.stdout.write(f"Active workers: {summary['workers']}")
|
396
|
+
|
397
|
+
if summary['queues']:
|
398
|
+
self.stdout.write("\nQueues:")
|
399
|
+
for queue_name, counts in summary['queues'].items():
|
400
|
+
self.stdout.write(f" {queue_name}: {counts['pending']} pending, {counts['failed']} failed")
|
401
|
+
else:
|
402
|
+
self.stdout.write("\nNo active queues found")
|
403
|
+
|
404
|
+
if summary['statistics']:
|
405
|
+
stats = summary['statistics']
|
406
|
+
self.stdout.write(f"\nStatistics:")
|
407
|
+
self.stdout.write(f" Total processed: {stats.get('total_processed', 'N/A')}")
|
408
|
+
self.stdout.write(f" Total failed: {stats.get('total_failed', 'N/A')}")
|
409
|
+
self.stdout.write(f" Avg processing time: {stats.get('processing_time_avg', 'N/A')}s")
|
410
|
+
|
411
|
+
def show_redis_keys(self, simulator: TaskSimulator):
|
412
|
+
"""Показать все Redis ключи."""
|
413
|
+
self.stdout.write("\n🔍 Redis Keys:")
|
414
|
+
keys = simulator.redis_client.keys("dramatiq:*")
|
415
|
+
|
416
|
+
if not keys:
|
417
|
+
self.stdout.write(" No Dramatiq keys found")
|
418
|
+
return
|
419
|
+
|
420
|
+
for key in sorted(keys):
|
421
|
+
key_type = simulator.redis_client.type(key)
|
422
|
+
if key_type == 'list':
|
423
|
+
length = simulator.redis_client.llen(key)
|
424
|
+
self.stdout.write(f" {key} (list): {length} items")
|
425
|
+
elif key_type == 'string':
|
426
|
+
ttl = simulator.redis_client.ttl(key)
|
427
|
+
ttl_str = f"TTL {ttl}s" if ttl > 0 else "no TTL"
|
428
|
+
self.stdout.write(f" {key} (string): {ttl_str}")
|
429
|
+
else:
|
430
|
+
self.stdout.write(f" {key} ({key_type})")
|