django-cfg 1.3.9__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 (187) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/payments/admin/networks_admin.py +12 -1
  3. django_cfg/apps/payments/admin/payments_admin.py +13 -0
  4. django_cfg/apps/payments/admin_interface/serializers/payment_serializers.py +62 -14
  5. django_cfg/apps/payments/admin_interface/templates/payments/components/payment_card.html +121 -0
  6. django_cfg/apps/payments/admin_interface/templates/payments/components/payment_qr_code.html +95 -0
  7. django_cfg/apps/payments/admin_interface/templates/payments/components/progress_bar.html +37 -0
  8. django_cfg/apps/payments/admin_interface/templates/payments/components/provider_stats.html +60 -0
  9. django_cfg/apps/payments/admin_interface/templates/payments/components/status_badge.html +41 -0
  10. django_cfg/apps/payments/admin_interface/templates/payments/components/status_overview.html +83 -0
  11. django_cfg/apps/payments/admin_interface/templates/payments/payment_detail.html +363 -0
  12. django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +33 -3
  13. django_cfg/apps/payments/admin_interface/views/api/payments.py +102 -0
  14. django_cfg/apps/payments/admin_interface/views/api/webhook_admin.py +96 -45
  15. django_cfg/apps/payments/admin_interface/views/forms.py +5 -1
  16. django_cfg/apps/payments/config/__init__.py +14 -15
  17. django_cfg/apps/payments/config/django_cfg_integration.py +59 -1
  18. django_cfg/apps/payments/config/helpers.py +8 -13
  19. django_cfg/apps/payments/migrations/0001_initial.py +33 -46
  20. django_cfg/apps/payments/migrations/0002_rename_payments_un_user_id_7f6e79_idx_payments_un_user_id_8ce187_idx_and_more.py +46 -0
  21. django_cfg/apps/payments/migrations/0003_universalpayment_status_changed_at.py +25 -0
  22. django_cfg/apps/payments/models/managers/payment_managers.py +142 -25
  23. django_cfg/apps/payments/models/payments.py +94 -0
  24. django_cfg/apps/payments/services/core/base.py +4 -4
  25. django_cfg/apps/payments/services/core/payment_service.py +265 -38
  26. django_cfg/apps/payments/services/providers/base.py +209 -3
  27. django_cfg/apps/payments/services/providers/models/__init__.py +2 -0
  28. django_cfg/apps/payments/services/providers/models/base.py +25 -2
  29. django_cfg/apps/payments/services/providers/nowpayments/models.py +2 -2
  30. django_cfg/apps/payments/services/providers/nowpayments/provider.py +57 -9
  31. django_cfg/apps/payments/services/providers/registry.py +5 -5
  32. django_cfg/apps/payments/services/types/requests.py +19 -7
  33. django_cfg/apps/payments/signals/payment_signals.py +31 -2
  34. django_cfg/apps/payments/static/payments/js/api-client.js +6 -1
  35. django_cfg/apps/payments/static/payments/js/payment-detail.js +167 -0
  36. django_cfg/apps/payments/static/payments/js/payment-form.js +35 -26
  37. django_cfg/apps/payments/templatetags/payment_tags.py +8 -0
  38. django_cfg/apps/payments/urls.py +3 -2
  39. django_cfg/apps/payments/views/api/currencies.py +3 -0
  40. django_cfg/apps/payments/views/serializers/currencies.py +18 -5
  41. django_cfg/apps/tasks/admin/tasks_admin.py +2 -2
  42. django_cfg/apps/tasks/static/tasks/css/dashboard.css +68 -217
  43. django_cfg/apps/tasks/static/tasks/js/api.js +40 -84
  44. django_cfg/apps/tasks/static/tasks/js/components/DataManager.js +24 -0
  45. django_cfg/apps/tasks/static/tasks/js/components/TabManager.js +85 -0
  46. django_cfg/apps/tasks/static/tasks/js/components/TaskRenderer.js +216 -0
  47. django_cfg/apps/tasks/static/tasks/js/dashboard/main.mjs +245 -0
  48. django_cfg/apps/tasks/static/tasks/js/dashboard/overview.mjs +123 -0
  49. django_cfg/apps/tasks/static/tasks/js/dashboard/queues.mjs +120 -0
  50. django_cfg/apps/tasks/static/tasks/js/dashboard/tasks.mjs +350 -0
  51. django_cfg/apps/tasks/static/tasks/js/dashboard/workers.mjs +169 -0
  52. django_cfg/apps/tasks/tasks/__init__.py +10 -0
  53. django_cfg/apps/tasks/tasks/demo_tasks.py +133 -0
  54. django_cfg/apps/tasks/templates/tasks/components/management_actions.html +42 -45
  55. django_cfg/apps/tasks/templates/tasks/components/{status_cards.html → overview_content.html} +30 -11
  56. django_cfg/apps/tasks/templates/tasks/components/queues_content.html +19 -0
  57. django_cfg/apps/tasks/templates/tasks/components/tab_navigation.html +16 -10
  58. django_cfg/apps/tasks/templates/tasks/components/tasks_content.html +51 -0
  59. django_cfg/apps/tasks/templates/tasks/components/workers_content.html +30 -0
  60. django_cfg/apps/tasks/templates/tasks/layout/base.html +117 -0
  61. django_cfg/apps/tasks/templates/tasks/pages/dashboard.html +82 -0
  62. django_cfg/apps/tasks/templates/tasks/partials/task_row_template.html +40 -0
  63. django_cfg/apps/tasks/templates/tasks/widgets/task_filters.html +37 -0
  64. django_cfg/apps/tasks/templates/tasks/widgets/task_footer.html +41 -0
  65. django_cfg/apps/tasks/templates/tasks/widgets/task_table.html +50 -0
  66. django_cfg/apps/tasks/urls.py +2 -2
  67. django_cfg/apps/tasks/urls_admin.py +2 -2
  68. django_cfg/apps/tasks/utils/__init__.py +1 -0
  69. django_cfg/apps/tasks/utils/simulator.py +356 -0
  70. django_cfg/apps/tasks/views/__init__.py +16 -0
  71. django_cfg/apps/tasks/views/api.py +569 -0
  72. django_cfg/apps/tasks/views/dashboard.py +58 -0
  73. django_cfg/core/integration/__init__.py +21 -0
  74. django_cfg/management/commands/rundramatiq_simulator.py +430 -0
  75. django_cfg/models/constance.py +0 -11
  76. django_cfg/models/payments.py +137 -3
  77. django_cfg/modules/django_tasks.py +54 -21
  78. django_cfg/registry/core.py +4 -9
  79. django_cfg/template_archive/django_sample.zip +0 -0
  80. {django_cfg-1.3.9.dist-info → django_cfg-1.3.11.dist-info}/METADATA +2 -2
  81. {django_cfg-1.3.9.dist-info → django_cfg-1.3.11.dist-info}/RECORD +84 -152
  82. django_cfg/apps/payments/config/constance/__init__.py +0 -22
  83. django_cfg/apps/payments/config/constance/config_service.py +0 -123
  84. django_cfg/apps/payments/config/constance/fields.py +0 -69
  85. django_cfg/apps/payments/config/constance/settings.py +0 -160
  86. django_cfg/apps/payments/migrations/0002_currency_usd_rate_currency_usd_rate_updated_at.py +0 -26
  87. django_cfg/apps/payments/migrations/0003_remove_provider_currency_fields.py +0 -28
  88. django_cfg/apps/payments/migrations/0004_add_reserved_usd_field.py +0 -30
  89. django_cfg/apps/tasks/static/tasks/js/dashboard.js +0 -614
  90. django_cfg/apps/tasks/static/tasks/js/modals.js +0 -452
  91. django_cfg/apps/tasks/static/tasks/js/notifications.js +0 -144
  92. django_cfg/apps/tasks/static/tasks/js/task-monitor.js +0 -454
  93. django_cfg/apps/tasks/static/tasks/js/theme.js +0 -77
  94. django_cfg/apps/tasks/templates/tasks/base.html +0 -96
  95. django_cfg/apps/tasks/templates/tasks/components/info_cards.html +0 -85
  96. django_cfg/apps/tasks/templates/tasks/components/overview_tab.html +0 -22
  97. django_cfg/apps/tasks/templates/tasks/components/queues_tab.html +0 -19
  98. django_cfg/apps/tasks/templates/tasks/components/task_details_modal.html +0 -103
  99. django_cfg/apps/tasks/templates/tasks/components/tasks_tab.html +0 -32
  100. django_cfg/apps/tasks/templates/tasks/components/workers_tab.html +0 -29
  101. django_cfg/apps/tasks/templates/tasks/dashboard.html +0 -29
  102. django_cfg/apps/tasks/views.py +0 -461
  103. django_cfg/management/commands/app_agent_diagnose.py +0 -470
  104. django_cfg/management/commands/app_agent_generate.py +0 -342
  105. django_cfg/management/commands/app_agent_info.py +0 -308
  106. django_cfg/management/commands/auto_generate.py +0 -486
  107. django_cfg/modules/django_app_agent/__init__.py +0 -87
  108. django_cfg/modules/django_app_agent/agents/__init__.py +0 -40
  109. django_cfg/modules/django_app_agent/agents/base/__init__.py +0 -24
  110. django_cfg/modules/django_app_agent/agents/base/agent.py +0 -354
  111. django_cfg/modules/django_app_agent/agents/base/context.py +0 -236
  112. django_cfg/modules/django_app_agent/agents/base/executor.py +0 -430
  113. django_cfg/modules/django_app_agent/agents/generation/__init__.py +0 -12
  114. django_cfg/modules/django_app_agent/agents/generation/app_generator/__init__.py +0 -15
  115. django_cfg/modules/django_app_agent/agents/generation/app_generator/config_validator.py +0 -147
  116. django_cfg/modules/django_app_agent/agents/generation/app_generator/main.py +0 -99
  117. django_cfg/modules/django_app_agent/agents/generation/app_generator/models.py +0 -32
  118. django_cfg/modules/django_app_agent/agents/generation/app_generator/prompt_manager.py +0 -290
  119. django_cfg/modules/django_app_agent/agents/interfaces.py +0 -376
  120. django_cfg/modules/django_app_agent/core/__init__.py +0 -33
  121. django_cfg/modules/django_app_agent/core/config.py +0 -300
  122. django_cfg/modules/django_app_agent/core/exceptions.py +0 -359
  123. django_cfg/modules/django_app_agent/models/__init__.py +0 -71
  124. django_cfg/modules/django_app_agent/models/base.py +0 -283
  125. django_cfg/modules/django_app_agent/models/context.py +0 -496
  126. django_cfg/modules/django_app_agent/models/enums.py +0 -481
  127. django_cfg/modules/django_app_agent/models/requests.py +0 -500
  128. django_cfg/modules/django_app_agent/models/responses.py +0 -585
  129. django_cfg/modules/django_app_agent/pytest.ini +0 -6
  130. django_cfg/modules/django_app_agent/services/__init__.py +0 -42
  131. django_cfg/modules/django_app_agent/services/app_generator/__init__.py +0 -30
  132. django_cfg/modules/django_app_agent/services/app_generator/ai_integration.py +0 -133
  133. django_cfg/modules/django_app_agent/services/app_generator/context.py +0 -40
  134. django_cfg/modules/django_app_agent/services/app_generator/main.py +0 -202
  135. django_cfg/modules/django_app_agent/services/app_generator/structure.py +0 -316
  136. django_cfg/modules/django_app_agent/services/app_generator/validation.py +0 -125
  137. django_cfg/modules/django_app_agent/services/base.py +0 -437
  138. django_cfg/modules/django_app_agent/services/context_builder/__init__.py +0 -34
  139. django_cfg/modules/django_app_agent/services/context_builder/code_extractor.py +0 -141
  140. django_cfg/modules/django_app_agent/services/context_builder/context_generator.py +0 -276
  141. django_cfg/modules/django_app_agent/services/context_builder/main.py +0 -272
  142. django_cfg/modules/django_app_agent/services/context_builder/models.py +0 -40
  143. django_cfg/modules/django_app_agent/services/context_builder/pattern_analyzer.py +0 -85
  144. django_cfg/modules/django_app_agent/services/project_scanner/__init__.py +0 -31
  145. django_cfg/modules/django_app_agent/services/project_scanner/app_discovery.py +0 -311
  146. django_cfg/modules/django_app_agent/services/project_scanner/main.py +0 -221
  147. django_cfg/modules/django_app_agent/services/project_scanner/models.py +0 -59
  148. django_cfg/modules/django_app_agent/services/project_scanner/pattern_detection.py +0 -94
  149. django_cfg/modules/django_app_agent/services/questioning_service/__init__.py +0 -28
  150. django_cfg/modules/django_app_agent/services/questioning_service/main.py +0 -273
  151. django_cfg/modules/django_app_agent/services/questioning_service/models.py +0 -111
  152. django_cfg/modules/django_app_agent/services/questioning_service/question_generator.py +0 -251
  153. django_cfg/modules/django_app_agent/services/questioning_service/response_processor.py +0 -347
  154. django_cfg/modules/django_app_agent/services/questioning_service/session_manager.py +0 -356
  155. django_cfg/modules/django_app_agent/services/report_service.py +0 -332
  156. django_cfg/modules/django_app_agent/services/template_manager/__init__.py +0 -18
  157. django_cfg/modules/django_app_agent/services/template_manager/jinja_engine.py +0 -236
  158. django_cfg/modules/django_app_agent/services/template_manager/main.py +0 -159
  159. django_cfg/modules/django_app_agent/services/template_manager/models.py +0 -36
  160. django_cfg/modules/django_app_agent/services/template_manager/template_loader.py +0 -100
  161. django_cfg/modules/django_app_agent/services/template_manager/templates/admin.py.j2 +0 -105
  162. django_cfg/modules/django_app_agent/services/template_manager/templates/apps.py.j2 +0 -31
  163. django_cfg/modules/django_app_agent/services/template_manager/templates/cfg_config.py.j2 +0 -44
  164. django_cfg/modules/django_app_agent/services/template_manager/templates/cfg_module.py.j2 +0 -81
  165. django_cfg/modules/django_app_agent/services/template_manager/templates/forms.py.j2 +0 -107
  166. django_cfg/modules/django_app_agent/services/template_manager/templates/models.py.j2 +0 -139
  167. django_cfg/modules/django_app_agent/services/template_manager/templates/serializers.py.j2 +0 -91
  168. django_cfg/modules/django_app_agent/services/template_manager/templates/tests.py.j2 +0 -195
  169. django_cfg/modules/django_app_agent/services/template_manager/templates/urls.py.j2 +0 -35
  170. django_cfg/modules/django_app_agent/services/template_manager/templates/views.py.j2 +0 -211
  171. django_cfg/modules/django_app_agent/services/template_manager/variable_processor.py +0 -200
  172. django_cfg/modules/django_app_agent/services/validation_service/__init__.py +0 -25
  173. django_cfg/modules/django_app_agent/services/validation_service/django_validator.py +0 -333
  174. django_cfg/modules/django_app_agent/services/validation_service/main.py +0 -242
  175. django_cfg/modules/django_app_agent/services/validation_service/models.py +0 -66
  176. django_cfg/modules/django_app_agent/services/validation_service/quality_validator.py +0 -352
  177. django_cfg/modules/django_app_agent/services/validation_service/security_validator.py +0 -272
  178. django_cfg/modules/django_app_agent/services/validation_service/syntax_validator.py +0 -203
  179. django_cfg/modules/django_app_agent/ui/__init__.py +0 -25
  180. django_cfg/modules/django_app_agent/ui/cli.py +0 -419
  181. django_cfg/modules/django_app_agent/ui/rich_components.py +0 -622
  182. django_cfg/modules/django_app_agent/utils/__init__.py +0 -38
  183. django_cfg/modules/django_app_agent/utils/logging.py +0 -360
  184. django_cfg/modules/django_app_agent/utils/validation.py +0 -417
  185. {django_cfg-1.3.9.dist-info → django_cfg-1.3.11.dist-info}/WHEEL +0 -0
  186. {django_cfg-1.3.9.dist-info → django_cfg-1.3.11.dist-info}/entry_points.txt +0 -0
  187. {django_cfg-1.3.9.dist-info → django_cfg-1.3.11.dist-info}/licenses/LICENSE +0 -0
@@ -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})")
@@ -181,17 +181,6 @@ class ConstanceConfig(BaseModel, BaseCfgAutoModule):
181
181
  except (ImportError, Exception):
182
182
  pass
183
183
 
184
- # Get fields from payments app (only if enabled)
185
- if config and config.payments and config.payments.enabled:
186
- try:
187
- from django_cfg.apps.payments.config import get_django_cfg_payments_constance_fields
188
- payments_fields = get_django_cfg_payments_constance_fields()
189
- app_fields.extend(payments_fields)
190
- print(f"✅ Added {len(payments_fields)} payments fields to Constance")
191
- except (ImportError, Exception) as e:
192
- print(f"❌ Failed to load payments constance fields: {e}")
193
- traceback.print_exc()
194
-
195
184
  # Cache the result
196
185
  self._app_fields_cache = app_fields
197
186
  return app_fields
@@ -6,14 +6,110 @@ Simple, clean configuration following django-cfg philosophy.
6
6
 
7
7
  from pydantic import BaseModel, Field
8
8
  from typing import List, Dict, Optional
9
+ from django_cfg.models.cfg import BaseCfgAutoModule
9
10
 
10
11
 
11
- class PaymentsConfig(BaseModel):
12
+ class BaseProviderConfig(BaseModel):
13
+ """Base configuration for payment providers."""
14
+
15
+ provider_name: str = Field(..., description="Provider name")
16
+ enabled: bool = Field(default=True, description="Whether provider is enabled")
17
+
18
+ def get_provider_config(self) -> Dict[str, any]:
19
+ """Get provider-specific configuration."""
20
+ return {
21
+ 'provider_name': self.provider_name,
22
+ 'enabled': self.enabled,
23
+ }
24
+
25
+
26
+ class NowPaymentsProviderConfig(BaseProviderConfig):
27
+ """NowPayments provider configuration."""
28
+
29
+ provider_name: str = Field(default="nowpayments", description="Provider name")
30
+ api_key: str = Field(default="", description="NowPayments API key")
31
+ ipn_secret: str = Field(default="", description="NowPayments IPN secret for webhook validation")
32
+ sandbox_mode: bool = Field(default=True, description="NowPayments sandbox mode")
33
+
34
+ def get_provider_config(self) -> Dict[str, any]:
35
+ """Get NowPayments-specific configuration."""
36
+ return {
37
+ 'provider_name': self.provider_name,
38
+ 'enabled': self.enabled and bool(self.api_key.strip()),
39
+ 'api_key': self.api_key,
40
+ 'ipn_secret': self.ipn_secret,
41
+ 'sandbox_mode': self.sandbox_mode,
42
+ }
43
+
44
+
45
+ # Future provider configs (commented out for now)
46
+ # class StripeProviderConfig(BaseProviderConfig):
47
+ # """Stripe provider configuration."""
48
+ #
49
+ # provider_name: str = Field(default="stripe", description="Provider name")
50
+ # api_key: str = Field(default="", description="Stripe API key")
51
+ # webhook_secret: str = Field(default="", description="Stripe webhook secret")
52
+ #
53
+ # def get_provider_config(self) -> Dict[str, any]:
54
+ # return {
55
+ # 'provider_name': self.provider_name,
56
+ # 'enabled': self.enabled and bool(self.api_key.strip()),
57
+ # 'api_key': self.api_key,
58
+ # 'webhook_secret': self.webhook_secret,
59
+ # }
60
+
61
+
62
+ class ProviderAPIKeysConfig(BaseModel):
63
+ """
64
+ API keys configuration for payment providers.
65
+
66
+ Stores list of provider configurations.
67
+ """
68
+
69
+ providers: List[BaseProviderConfig] = Field(
70
+ default_factory=list,
71
+ description="List of provider configurations"
72
+ )
73
+
74
+ def add_provider(self, provider_config: BaseProviderConfig):
75
+ """Add a provider configuration."""
76
+ # Remove existing provider with same name
77
+ self.providers = [p for p in self.providers if p.provider_name != provider_config.provider_name]
78
+ self.providers.append(provider_config)
79
+
80
+ def get_provider_config(self, provider: str) -> Dict[str, any]:
81
+ """Get provider-specific configuration."""
82
+ provider_lower = provider.lower()
83
+
84
+ for provider_config in self.providers:
85
+ if provider_config.provider_name.lower() == provider_lower:
86
+ return provider_config.get_provider_config()
87
+
88
+ return {'enabled': False, 'provider_name': provider}
89
+
90
+ def get_enabled_providers(self) -> List[str]:
91
+ """Get list of enabled providers."""
92
+ enabled = []
93
+ for provider_config in self.providers:
94
+ config = provider_config.get_provider_config()
95
+ if config.get('enabled', False):
96
+ enabled.append(provider_config.provider_name)
97
+ return enabled
98
+
99
+ def get_provider_by_name(self, provider_name: str) -> Optional[BaseProviderConfig]:
100
+ """Get provider configuration by name."""
101
+ for provider_config in self.providers:
102
+ if provider_config.provider_name.lower() == provider_name.lower():
103
+ return provider_config
104
+ return None
105
+
106
+
107
+ class PaymentsConfig(BaseModel, BaseCfgAutoModule):
12
108
  """
13
109
  Payments app configuration for django-cfg.
14
110
 
15
- Static configuration that doesn't change at runtime.
16
- Dynamic settings (API keys, secrets) are handled by Constance.
111
+ Includes both static configuration and API keys.
112
+ API keys are now managed through BaseCfgAutoModule instead of Constance.
17
113
  """
18
114
 
19
115
  # Core settings
@@ -22,6 +118,12 @@ class PaymentsConfig(BaseModel):
22
118
  description="Enable payments app"
23
119
  )
24
120
 
121
+ # API keys configuration
122
+ api_keys: ProviderAPIKeysConfig = Field(
123
+ default_factory=ProviderAPIKeysConfig,
124
+ description="API keys and secrets for payment providers"
125
+ )
126
+
25
127
  # Middleware settings
26
128
  middleware_enabled: bool = Field(
27
129
  default=True,
@@ -207,6 +309,29 @@ class PaymentsConfig(BaseModel):
207
309
 
208
310
  return items
209
311
 
312
+ # API Keys access methods
313
+ def get_provider_api_config(self, provider: str) -> Dict[str, any]:
314
+ """Get provider-specific API configuration."""
315
+ return self.api_keys.get_provider_config(provider)
316
+
317
+ def get_enabled_providers(self) -> List[str]:
318
+ """Get list of enabled providers (those with API keys configured)."""
319
+ return self.api_keys.get_enabled_providers()
320
+
321
+ def is_provider_enabled(self, provider: str) -> bool:
322
+ """Check if a specific provider is enabled (has API keys configured)."""
323
+ config = self.api_keys.get_provider_config(provider)
324
+ return config.get('enabled', False)
325
+
326
+ # BaseCfgAutoModule implementation
327
+ def get_smart_defaults(self):
328
+ """Get smart default configuration for this module."""
329
+ return PaymentsConfig()
330
+
331
+ def get_module_config(self):
332
+ """Get the final configuration for this module."""
333
+ return self
334
+
210
335
  @classmethod
211
336
  def get_current_config(cls) -> 'PaymentsConfig':
212
337
  """
@@ -220,3 +345,12 @@ class PaymentsConfig(BaseModel):
220
345
  return PaymentsConfigManager.get_payments_config_safe()
221
346
  except Exception:
222
347
  return cls()
348
+
349
+
350
+ # Export all payment configuration classes
351
+ __all__ = [
352
+ "BaseProviderConfig",
353
+ "NowPaymentsProviderConfig",
354
+ "ProviderAPIKeysConfig",
355
+ "PaymentsConfig",
356
+ ]