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.
Files changed (246) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/accounts/admin/__init__.py +24 -8
  3. django_cfg/apps/accounts/admin/activity_admin.py +146 -0
  4. django_cfg/apps/accounts/admin/filters.py +98 -22
  5. django_cfg/apps/accounts/admin/group_admin.py +86 -0
  6. django_cfg/apps/accounts/admin/inlines.py +42 -13
  7. django_cfg/apps/accounts/admin/otp_admin.py +115 -0
  8. django_cfg/apps/accounts/admin/registration_admin.py +173 -0
  9. django_cfg/apps/accounts/admin/resources.py +123 -19
  10. django_cfg/apps/accounts/admin/twilio_admin.py +327 -0
  11. django_cfg/apps/accounts/admin/user_admin.py +362 -0
  12. django_cfg/apps/agents/admin/__init__.py +17 -4
  13. django_cfg/apps/agents/admin/execution_admin.py +204 -183
  14. django_cfg/apps/agents/admin/registry_admin.py +230 -255
  15. django_cfg/apps/agents/admin/toolsets_admin.py +274 -321
  16. django_cfg/apps/agents/core/__init__.py +1 -1
  17. django_cfg/apps/agents/core/django_agent.py +221 -0
  18. django_cfg/apps/agents/core/exceptions.py +14 -0
  19. django_cfg/apps/agents/core/orchestrator.py +18 -3
  20. django_cfg/apps/knowbase/admin/__init__.py +1 -1
  21. django_cfg/apps/knowbase/admin/archive_admin.py +352 -640
  22. django_cfg/apps/knowbase/admin/chat_admin.py +258 -192
  23. django_cfg/apps/knowbase/admin/document_admin.py +269 -262
  24. django_cfg/apps/knowbase/admin/external_data_admin.py +271 -489
  25. django_cfg/apps/knowbase/config/settings.py +21 -4
  26. django_cfg/apps/knowbase/views/chat_views.py +3 -0
  27. django_cfg/apps/leads/admin/__init__.py +3 -1
  28. django_cfg/apps/leads/admin/leads_admin.py +235 -35
  29. django_cfg/apps/maintenance/admin/__init__.py +2 -2
  30. django_cfg/apps/maintenance/admin/api_key_admin.py +125 -63
  31. django_cfg/apps/maintenance/admin/log_admin.py +143 -61
  32. django_cfg/apps/maintenance/admin/scheduled_admin.py +212 -301
  33. django_cfg/apps/maintenance/admin/site_admin.py +213 -352
  34. django_cfg/apps/newsletter/admin/__init__.py +29 -2
  35. django_cfg/apps/newsletter/admin/newsletter_admin.py +531 -193
  36. django_cfg/apps/payments/admin/__init__.py +18 -27
  37. django_cfg/apps/payments/admin/api_keys_admin.py +179 -546
  38. django_cfg/apps/payments/admin/balance_admin.py +166 -632
  39. django_cfg/apps/payments/admin/currencies_admin.py +235 -607
  40. django_cfg/apps/payments/admin/endpoint_groups_admin.py +127 -0
  41. django_cfg/apps/payments/admin/filters.py +83 -3
  42. django_cfg/apps/payments/admin/networks_admin.py +269 -0
  43. django_cfg/apps/payments/admin/payments_admin.py +183 -460
  44. django_cfg/apps/payments/admin/subscriptions_admin.py +119 -636
  45. django_cfg/apps/payments/admin/tariffs_admin.py +248 -0
  46. django_cfg/apps/payments/admin_interface/serializers/payment_serializers.py +153 -34
  47. django_cfg/apps/payments/admin_interface/templates/payments/components/payment_card.html +121 -0
  48. django_cfg/apps/payments/admin_interface/templates/payments/components/payment_qr_code.html +95 -0
  49. django_cfg/apps/payments/admin_interface/templates/payments/components/progress_bar.html +37 -0
  50. django_cfg/apps/payments/admin_interface/templates/payments/components/provider_stats.html +60 -0
  51. django_cfg/apps/payments/admin_interface/templates/payments/components/status_badge.html +41 -0
  52. django_cfg/apps/payments/admin_interface/templates/payments/components/status_overview.html +83 -0
  53. django_cfg/apps/payments/admin_interface/templates/payments/payment_detail.html +363 -0
  54. django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +43 -17
  55. django_cfg/apps/payments/admin_interface/views/__init__.py +2 -0
  56. django_cfg/apps/payments/admin_interface/views/api/payments.py +102 -0
  57. django_cfg/apps/payments/admin_interface/views/api/webhook_admin.py +109 -63
  58. django_cfg/apps/payments/admin_interface/views/forms.py +5 -1
  59. django_cfg/apps/payments/config/__init__.py +14 -15
  60. django_cfg/apps/payments/config/django_cfg_integration.py +59 -1
  61. django_cfg/apps/payments/config/helpers.py +8 -13
  62. django_cfg/apps/payments/management/commands/manage_currencies.py +236 -274
  63. django_cfg/apps/payments/management/commands/manage_providers.py +4 -1
  64. django_cfg/apps/payments/middleware/api_access.py +32 -6
  65. django_cfg/apps/payments/migrations/0001_initial.py +33 -46
  66. django_cfg/apps/payments/migrations/0002_rename_payments_un_user_id_7f6e79_idx_payments_un_user_id_8ce187_idx_and_more.py +46 -0
  67. django_cfg/apps/payments/migrations/0003_universalpayment_status_changed_at.py +25 -0
  68. django_cfg/apps/payments/models/balance.py +12 -0
  69. django_cfg/apps/payments/models/currencies.py +106 -32
  70. django_cfg/apps/payments/models/managers/currency_managers.py +65 -0
  71. django_cfg/apps/payments/models/managers/payment_managers.py +142 -25
  72. django_cfg/apps/payments/models/payments.py +94 -0
  73. django_cfg/apps/payments/services/core/base.py +4 -4
  74. django_cfg/apps/payments/services/core/currency_service.py +35 -28
  75. django_cfg/apps/payments/services/core/payment_service.py +266 -39
  76. django_cfg/apps/payments/services/providers/__init__.py +3 -0
  77. django_cfg/apps/payments/services/providers/base.py +303 -41
  78. django_cfg/apps/payments/services/providers/models/__init__.py +42 -0
  79. django_cfg/apps/payments/services/providers/models/base.py +145 -0
  80. django_cfg/apps/payments/services/providers/models/providers.py +87 -0
  81. django_cfg/apps/payments/services/providers/models/universal.py +48 -0
  82. django_cfg/apps/payments/services/providers/nowpayments/__init__.py +31 -0
  83. django_cfg/apps/payments/services/providers/nowpayments/config.py +70 -0
  84. django_cfg/apps/payments/services/providers/nowpayments/models.py +150 -0
  85. django_cfg/apps/payments/services/providers/nowpayments/parsers.py +879 -0
  86. django_cfg/apps/payments/services/providers/nowpayments/provider.py +557 -0
  87. django_cfg/apps/payments/services/providers/nowpayments/sync.py +196 -0
  88. django_cfg/apps/payments/services/providers/registry.py +9 -37
  89. django_cfg/apps/payments/services/providers/sync_service.py +277 -0
  90. django_cfg/apps/payments/services/types/requests.py +19 -7
  91. django_cfg/apps/payments/signals/payment_signals.py +31 -2
  92. django_cfg/apps/payments/static/payments/js/api-client.js +29 -6
  93. django_cfg/apps/payments/static/payments/js/payment-detail.js +167 -0
  94. django_cfg/apps/payments/static/payments/js/payment-form.js +98 -32
  95. django_cfg/apps/payments/tasks/__init__.py +39 -0
  96. django_cfg/apps/payments/tasks/types.py +73 -0
  97. django_cfg/apps/payments/tasks/usage_tracking.py +308 -0
  98. django_cfg/apps/payments/templates/admin/payments/_components/dashboard_header.html +23 -0
  99. django_cfg/apps/payments/templates/admin/payments/_components/stats_card.html +25 -0
  100. django_cfg/apps/payments/templates/admin/payments/_components/stats_grid.html +16 -0
  101. django_cfg/apps/payments/templates/admin/payments/apikey/change_list.html +39 -0
  102. django_cfg/apps/payments/templates/admin/payments/balance/change_list.html +50 -0
  103. django_cfg/apps/payments/templates/admin/payments/currency/change_list.html +40 -0
  104. django_cfg/apps/payments/templates/admin/payments/payment/change_list.html +48 -0
  105. django_cfg/apps/payments/templates/admin/payments/subscription/change_list.html +48 -0
  106. django_cfg/apps/payments/templatetags/payment_tags.py +8 -0
  107. django_cfg/apps/payments/urls.py +3 -2
  108. django_cfg/apps/payments/urls_admin.py +1 -1
  109. django_cfg/apps/payments/views/api/currencies.py +8 -5
  110. django_cfg/apps/payments/views/overview/services.py +2 -2
  111. django_cfg/apps/payments/views/serializers/currencies.py +22 -8
  112. django_cfg/apps/support/admin/__init__.py +10 -1
  113. django_cfg/apps/support/admin/support_admin.py +338 -141
  114. django_cfg/apps/tasks/admin/__init__.py +11 -0
  115. django_cfg/apps/tasks/admin/tasks_admin.py +430 -0
  116. django_cfg/apps/tasks/static/tasks/css/dashboard.css +68 -217
  117. django_cfg/apps/tasks/static/tasks/js/api.js +40 -84
  118. django_cfg/apps/tasks/static/tasks/js/components/DataManager.js +24 -0
  119. django_cfg/apps/tasks/static/tasks/js/components/TabManager.js +85 -0
  120. django_cfg/apps/tasks/static/tasks/js/components/TaskRenderer.js +216 -0
  121. django_cfg/apps/tasks/static/tasks/js/dashboard/main.mjs +245 -0
  122. django_cfg/apps/tasks/static/tasks/js/dashboard/overview.mjs +123 -0
  123. django_cfg/apps/tasks/static/tasks/js/dashboard/queues.mjs +120 -0
  124. django_cfg/apps/tasks/static/tasks/js/dashboard/tasks.mjs +350 -0
  125. django_cfg/apps/tasks/static/tasks/js/dashboard/workers.mjs +169 -0
  126. django_cfg/apps/tasks/tasks/__init__.py +10 -0
  127. django_cfg/apps/tasks/tasks/demo_tasks.py +133 -0
  128. django_cfg/apps/tasks/templates/tasks/components/management_actions.html +42 -45
  129. django_cfg/apps/tasks/templates/tasks/components/{status_cards.html → overview_content.html} +30 -11
  130. django_cfg/apps/tasks/templates/tasks/components/queues_content.html +19 -0
  131. django_cfg/apps/tasks/templates/tasks/components/tab_navigation.html +16 -10
  132. django_cfg/apps/tasks/templates/tasks/components/tasks_content.html +51 -0
  133. django_cfg/apps/tasks/templates/tasks/components/workers_content.html +30 -0
  134. django_cfg/apps/tasks/templates/tasks/layout/base.html +117 -0
  135. django_cfg/apps/tasks/templates/tasks/pages/dashboard.html +82 -0
  136. django_cfg/apps/tasks/templates/tasks/partials/task_row_template.html +40 -0
  137. django_cfg/apps/tasks/templates/tasks/widgets/task_filters.html +37 -0
  138. django_cfg/apps/tasks/templates/tasks/widgets/task_footer.html +41 -0
  139. django_cfg/apps/tasks/templates/tasks/widgets/task_table.html +50 -0
  140. django_cfg/apps/tasks/urls.py +2 -2
  141. django_cfg/apps/tasks/urls_admin.py +2 -2
  142. django_cfg/apps/tasks/utils/__init__.py +1 -0
  143. django_cfg/apps/tasks/utils/simulator.py +356 -0
  144. django_cfg/apps/tasks/views/__init__.py +16 -0
  145. django_cfg/apps/tasks/views/api.py +569 -0
  146. django_cfg/apps/tasks/views/dashboard.py +58 -0
  147. django_cfg/config.py +1 -1
  148. django_cfg/core/config.py +10 -5
  149. django_cfg/core/generation.py +1 -1
  150. django_cfg/core/integration/__init__.py +21 -0
  151. django_cfg/management/commands/__init__.py +13 -1
  152. django_cfg/management/commands/migrate_all.py +9 -3
  153. django_cfg/management/commands/migrator.py +11 -6
  154. django_cfg/management/commands/rundramatiq.py +3 -2
  155. django_cfg/management/commands/rundramatiq_simulator.py +430 -0
  156. django_cfg/middleware/__init__.py +0 -2
  157. django_cfg/models/api_keys.py +115 -0
  158. django_cfg/models/constance.py +0 -11
  159. django_cfg/models/payments.py +137 -3
  160. django_cfg/modules/django_admin/__init__.py +64 -0
  161. django_cfg/modules/django_admin/decorators/__init__.py +13 -0
  162. django_cfg/modules/django_admin/decorators/actions.py +106 -0
  163. django_cfg/modules/django_admin/decorators/display.py +106 -0
  164. django_cfg/modules/django_admin/mixins/__init__.py +14 -0
  165. django_cfg/modules/django_admin/mixins/display_mixin.py +81 -0
  166. django_cfg/modules/django_admin/mixins/optimization_mixin.py +41 -0
  167. django_cfg/modules/django_admin/mixins/standalone_actions_mixin.py +202 -0
  168. django_cfg/modules/django_admin/models/__init__.py +20 -0
  169. django_cfg/modules/django_admin/models/action_models.py +33 -0
  170. django_cfg/modules/django_admin/models/badge_models.py +20 -0
  171. django_cfg/modules/django_admin/models/base.py +26 -0
  172. django_cfg/modules/django_admin/models/display_models.py +31 -0
  173. django_cfg/modules/django_admin/utils/badges.py +159 -0
  174. django_cfg/modules/django_admin/utils/displays.py +247 -0
  175. django_cfg/modules/django_currency/__init__.py +2 -2
  176. django_cfg/modules/django_currency/clients/__init__.py +2 -2
  177. django_cfg/modules/django_currency/clients/hybrid_client.py +587 -0
  178. django_cfg/modules/django_currency/core/converter.py +12 -12
  179. django_cfg/modules/django_currency/database/__init__.py +2 -2
  180. django_cfg/modules/django_currency/database/database_loader.py +93 -42
  181. django_cfg/modules/django_llm/llm/client.py +10 -2
  182. django_cfg/modules/django_tasks.py +54 -21
  183. django_cfg/modules/django_unfold/callbacks/actions.py +1 -1
  184. django_cfg/modules/django_unfold/callbacks/statistics.py +1 -1
  185. django_cfg/modules/django_unfold/dashboard.py +14 -13
  186. django_cfg/modules/django_unfold/models/config.py +1 -1
  187. django_cfg/registry/core.py +7 -9
  188. django_cfg/registry/third_party.py +2 -2
  189. django_cfg/template_archive/django_sample.zip +0 -0
  190. {django_cfg-1.3.7.dist-info → django_cfg-1.3.11.dist-info}/METADATA +2 -1
  191. {django_cfg-1.3.7.dist-info → django_cfg-1.3.11.dist-info}/RECORD +198 -160
  192. django_cfg/apps/accounts/admin/activity.py +0 -96
  193. django_cfg/apps/accounts/admin/group.py +0 -17
  194. django_cfg/apps/accounts/admin/otp.py +0 -59
  195. django_cfg/apps/accounts/admin/registration_source.py +0 -97
  196. django_cfg/apps/accounts/admin/twilio_response.py +0 -227
  197. django_cfg/apps/accounts/admin/user.py +0 -300
  198. django_cfg/apps/agents/core/agent.py +0 -281
  199. django_cfg/apps/payments/admin_interface/old/payments/base.html +0 -175
  200. django_cfg/apps/payments/admin_interface/old/payments/components/dev_tool_card.html +0 -125
  201. django_cfg/apps/payments/admin_interface/old/payments/components/loading_spinner.html +0 -16
  202. django_cfg/apps/payments/admin_interface/old/payments/components/ngrok_status_card.html +0 -113
  203. django_cfg/apps/payments/admin_interface/old/payments/components/notification.html +0 -27
  204. django_cfg/apps/payments/admin_interface/old/payments/components/provider_card.html +0 -86
  205. django_cfg/apps/payments/admin_interface/old/payments/components/status_card.html +0 -35
  206. django_cfg/apps/payments/admin_interface/old/payments/currency_converter.html +0 -382
  207. django_cfg/apps/payments/admin_interface/old/payments/payment_dashboard.html +0 -309
  208. django_cfg/apps/payments/admin_interface/old/payments/payment_form.html +0 -303
  209. django_cfg/apps/payments/admin_interface/old/payments/payment_list.html +0 -382
  210. django_cfg/apps/payments/admin_interface/old/payments/payment_status.html +0 -500
  211. django_cfg/apps/payments/admin_interface/old/payments/webhook_dashboard.html +0 -518
  212. django_cfg/apps/payments/admin_interface/old/static/payments/css/components.css +0 -619
  213. django_cfg/apps/payments/admin_interface/old/static/payments/css/dashboard.css +0 -188
  214. django_cfg/apps/payments/admin_interface/old/static/payments/js/components.js +0 -545
  215. django_cfg/apps/payments/admin_interface/old/static/payments/js/ngrok-status.js +0 -163
  216. django_cfg/apps/payments/admin_interface/old/static/payments/js/utils.js +0 -412
  217. django_cfg/apps/payments/config/constance/__init__.py +0 -22
  218. django_cfg/apps/payments/config/constance/config_service.py +0 -123
  219. django_cfg/apps/payments/config/constance/fields.py +0 -69
  220. django_cfg/apps/payments/config/constance/settings.py +0 -160
  221. django_cfg/apps/payments/services/providers/nowpayments.py +0 -478
  222. django_cfg/apps/tasks/admin.py +0 -320
  223. django_cfg/apps/tasks/static/tasks/js/dashboard.js +0 -614
  224. django_cfg/apps/tasks/static/tasks/js/modals.js +0 -452
  225. django_cfg/apps/tasks/static/tasks/js/notifications.js +0 -144
  226. django_cfg/apps/tasks/static/tasks/js/task-monitor.js +0 -454
  227. django_cfg/apps/tasks/static/tasks/js/theme.js +0 -77
  228. django_cfg/apps/tasks/templates/tasks/base.html +0 -96
  229. django_cfg/apps/tasks/templates/tasks/components/info_cards.html +0 -85
  230. django_cfg/apps/tasks/templates/tasks/components/overview_tab.html +0 -22
  231. django_cfg/apps/tasks/templates/tasks/components/queues_tab.html +0 -19
  232. django_cfg/apps/tasks/templates/tasks/components/task_details_modal.html +0 -103
  233. django_cfg/apps/tasks/templates/tasks/components/tasks_tab.html +0 -32
  234. django_cfg/apps/tasks/templates/tasks/components/workers_tab.html +0 -29
  235. django_cfg/apps/tasks/templates/tasks/dashboard.html +0 -29
  236. django_cfg/apps/tasks/views.py +0 -461
  237. django_cfg/management/commands/auto_generate.py +0 -486
  238. django_cfg/middleware/static_nocache.py +0 -55
  239. django_cfg/modules/django_currency/clients/yahoo_client.py +0 -157
  240. /django_cfg/modules/{django_unfold → django_admin}/icons/README.md +0 -0
  241. /django_cfg/modules/{django_unfold → django_admin}/icons/__init__.py +0 -0
  242. /django_cfg/modules/{django_unfold → django_admin}/icons/constants.py +0 -0
  243. /django_cfg/modules/{django_unfold → django_admin}/icons/generate_icons.py +0 -0
  244. {django_cfg-1.3.7.dist-info → django_cfg-1.3.11.dist-info}/WHEEL +0 -0
  245. {django_cfg-1.3.7.dist-info → django_cfg-1.3.11.dist-info}/entry_points.txt +0 -0
  246. {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
- # Django management commands
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.WARNING(f" ⚠️ Warning: {e}"))
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.WARNING(f" ⚠️ Warning: {e}"))
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.WARNING(f"⚠️ Constance warning: {e}"))
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.stdout.write(self.style.WARNING(f" ⚠️ Warning migrating {app}: {e}"))
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.stdout.write(self.style.ERROR(f"Error migrating {db_name}: {e}"))
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.stdout.write(self.style.WARNING(f"⚠️ Constance migration warning: {e}"))
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.stdout.write(self.style.WARNING(f"⚠️ Could not migrate constance: {e}"))
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.stdout.write(self.style.WARNING(f" ⚠️ Warning: {e}"))
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.stdout.write(self.style.ERROR(f"Error getting Django config info: {e}"))
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})")
@@ -5,9 +5,7 @@ Provides middleware components for Django CFG applications.
5
5
  """
6
6
 
7
7
  from .user_activity import UserActivityMiddleware
8
- from .static_nocache import StaticNoCacheMiddleware
9
8
 
10
9
  __all__ = [
11
10
  'UserActivityMiddleware',
12
- 'StaticNoCacheMiddleware',
13
11
  ]