django-cfg 1.4.106__py3-none-any.whl → 1.4.108__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of django-cfg might be problematic. Click here for more details.

Files changed (137) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/accounts/views/profile.py +19 -9
  3. django_cfg/apps/centrifugo/views/admin_api.py +4 -7
  4. django_cfg/apps/centrifugo/views/monitoring.py +3 -6
  5. django_cfg/apps/centrifugo/views/testing_api.py +3 -6
  6. django_cfg/apps/dashboard/services/system_health_service.py +16 -11
  7. django_cfg/apps/dashboard/views/activity_views.py +3 -5
  8. django_cfg/apps/dashboard/views/apizones_views.py +4 -5
  9. django_cfg/apps/dashboard/views/charts_views.py +4 -5
  10. django_cfg/apps/dashboard/views/overview_views.py +4 -5
  11. django_cfg/apps/dashboard/views/statistics_views.py +4 -5
  12. django_cfg/apps/dashboard/views/system_views.py +4 -5
  13. django_cfg/apps/knowbase/__init__.py +2 -2
  14. django_cfg/apps/knowbase/apps.py +2 -8
  15. django_cfg/apps/knowbase/views/base.py +9 -4
  16. django_cfg/apps/support/views/api.py +16 -7
  17. django_cfg/apps/tasks/__init__.py +61 -2
  18. django_cfg/apps/tasks/admin/__init__.py +3 -10
  19. django_cfg/apps/tasks/admin/config.py +98 -0
  20. django_cfg/apps/tasks/admin/task_log.py +265 -0
  21. django_cfg/apps/tasks/apps.py +7 -9
  22. django_cfg/apps/tasks/filters/__init__.py +10 -0
  23. django_cfg/apps/tasks/filters/task_log.py +121 -0
  24. django_cfg/apps/tasks/migrations/0001_initial.py +196 -0
  25. django_cfg/apps/tasks/models/__init__.py +4 -0
  26. django_cfg/apps/tasks/models/task_log.py +246 -0
  27. django_cfg/apps/tasks/serializers/__init__.py +28 -0
  28. django_cfg/apps/tasks/serializers/task_log.py +249 -0
  29. django_cfg/apps/tasks/services/__init__.py +10 -0
  30. django_cfg/apps/tasks/services/client/__init__.py +7 -0
  31. django_cfg/apps/tasks/services/client/client.py +234 -0
  32. django_cfg/apps/tasks/services/config_helper.py +63 -0
  33. django_cfg/apps/tasks/services/sync.py +204 -0
  34. django_cfg/apps/tasks/urls.py +7 -13
  35. django_cfg/apps/tasks/views/__init__.py +4 -10
  36. django_cfg/apps/tasks/views/task_log.py +41 -0
  37. django_cfg/apps/tasks/views/task_log_base.py +41 -0
  38. django_cfg/apps/tasks/views/task_log_overview.py +100 -0
  39. django_cfg/apps/tasks/views/task_log_related.py +41 -0
  40. django_cfg/apps/tasks/views/task_log_stats.py +91 -0
  41. django_cfg/apps/tasks/views/task_log_timeline.py +81 -0
  42. django_cfg/apps/urls.py +0 -1
  43. django_cfg/cli/commands/info.py +1 -1
  44. django_cfg/cli/utils.py +1 -1
  45. django_cfg/core/base/config_model.py +1 -1
  46. django_cfg/core/builders/apps_builder.py +1 -1
  47. django_cfg/core/generation/integration_generators/__init__.py +1 -1
  48. django_cfg/core/generation/integration_generators/tasks.py +14 -18
  49. django_cfg/core/generation/security_generators/crypto_fields.py +2 -1
  50. django_cfg/core/integration/display/startup.py +1 -1
  51. django_cfg/mixins/__init__.py +12 -0
  52. django_cfg/mixins/admin_api.py +37 -0
  53. django_cfg/mixins/client_api.py +39 -0
  54. django_cfg/models/django/constance.py +2 -8
  55. django_cfg/models/django/crypto_fields.py +13 -48
  56. django_cfg/models/tasks/__init__.py +8 -10
  57. django_cfg/models/tasks/backends.py +76 -207
  58. django_cfg/models/tasks/config.py +20 -127
  59. django_cfg/models/tasks/utils.py +17 -29
  60. django_cfg/modules/django_admin/RESOURCE_CONFIG_ENHANCEMENT.md +350 -0
  61. django_cfg/modules/django_admin/__init__.py +4 -0
  62. django_cfg/modules/django_admin/base/pydantic_admin.py +70 -15
  63. django_cfg/modules/django_admin/config/__init__.py +4 -0
  64. django_cfg/modules/django_admin/config/admin_config.py +13 -1
  65. django_cfg/modules/django_admin/config/background_task_config.py +76 -0
  66. django_cfg/modules/django_admin/config/resource_config.py +129 -0
  67. django_cfg/modules/django_client/management/commands/generate_client.py +13 -1
  68. django_cfg/modules/django_unfold/navigation.py +121 -22
  69. django_cfg/pyproject.toml +2 -2
  70. django_cfg/registry/core.py +1 -1
  71. django_cfg/static/frontend/admin.zip +0 -0
  72. {django_cfg-1.4.106.dist-info → django_cfg-1.4.108.dist-info}/METADATA +5 -3
  73. {django_cfg-1.4.106.dist-info → django_cfg-1.4.108.dist-info}/RECORD +77 -111
  74. django_cfg/apps/tasks/admin/actions.py +0 -29
  75. django_cfg/apps/tasks/admin/tasks_admin.py +0 -154
  76. django_cfg/apps/tasks/api/serializers.py +0 -82
  77. django_cfg/apps/tasks/api/views.py +0 -571
  78. django_cfg/apps/tasks/serializers.py +0 -82
  79. django_cfg/apps/tasks/static/tasks/css/dashboard-alpine.css +0 -299
  80. django_cfg/apps/tasks/static/tasks/css/dashboard.css +0 -120
  81. django_cfg/apps/tasks/static/tasks/js/alpine/README.md +0 -47
  82. django_cfg/apps/tasks/static/tasks/js/alpine/actions/index.js +0 -8
  83. django_cfg/apps/tasks/static/tasks/js/alpine/actions/management.js +0 -123
  84. django_cfg/apps/tasks/static/tasks/js/alpine/actions/pagination.js +0 -21
  85. django_cfg/apps/tasks/static/tasks/js/alpine/actions/tasks.js +0 -101
  86. django_cfg/apps/tasks/static/tasks/js/alpine/actions/workers.js +0 -59
  87. django_cfg/apps/tasks/static/tasks/js/alpine/computed.js +0 -35
  88. django_cfg/apps/tasks/static/tasks/js/alpine/index.js +0 -148
  89. django_cfg/apps/tasks/static/tasks/js/alpine/loaders/index.js +0 -36
  90. django_cfg/apps/tasks/static/tasks/js/alpine/loaders/overview.js +0 -37
  91. django_cfg/apps/tasks/static/tasks/js/alpine/loaders/queues.js +0 -27
  92. django_cfg/apps/tasks/static/tasks/js/alpine/loaders/tasks.js +0 -32
  93. django_cfg/apps/tasks/static/tasks/js/alpine/loaders/workers.js +0 -21
  94. django_cfg/apps/tasks/static/tasks/js/alpine/state.js +0 -36
  95. django_cfg/apps/tasks/static/tasks/js/alpine/utils/formatters.js +0 -42
  96. django_cfg/apps/tasks/static/tasks/js/alpine/utils/helpers.js +0 -68
  97. django_cfg/apps/tasks/static/tasks/js/dashboard-alpine.js +0 -725
  98. django_cfg/apps/tasks/tasks/__init__.py +0 -10
  99. django_cfg/apps/tasks/tasks/demo_tasks.py +0 -127
  100. django_cfg/apps/tasks/templates/tasks/components/management_actions.html +0 -71
  101. django_cfg/apps/tasks/templates/tasks/components/overview_content.html +0 -94
  102. django_cfg/apps/tasks/templates/tasks/components/queues_content.html +0 -44
  103. django_cfg/apps/tasks/templates/tasks/components/tab_navigation.html +0 -45
  104. django_cfg/apps/tasks/templates/tasks/components/task_details_modal.html +0 -151
  105. django_cfg/apps/tasks/templates/tasks/components/tasks_content.html +0 -61
  106. django_cfg/apps/tasks/templates/tasks/components/tasks_mjs_integration.html +0 -269
  107. django_cfg/apps/tasks/templates/tasks/components/workers_content.html +0 -60
  108. django_cfg/apps/tasks/templates/tasks/layout/base.html +0 -20
  109. django_cfg/apps/tasks/templates/tasks/pages/dashboard-improved.html +0 -168
  110. django_cfg/apps/tasks/templates/tasks/pages/dashboard.html +0 -77
  111. django_cfg/apps/tasks/templates/tasks/partials/task_row_template.html +0 -40
  112. django_cfg/apps/tasks/templates/tasks/widgets/task_filters.html +0 -40
  113. django_cfg/apps/tasks/templates/tasks/widgets/task_footer.html +0 -86
  114. django_cfg/apps/tasks/templates/tasks/widgets/task_table.html +0 -90
  115. django_cfg/apps/tasks/urls_admin.py +0 -15
  116. django_cfg/apps/tasks/utils/__init__.py +0 -1
  117. django_cfg/apps/tasks/utils/simulator.py +0 -353
  118. django_cfg/apps/tasks/views/api.py +0 -571
  119. django_cfg/apps/tasks/views/dashboard.py +0 -89
  120. django_cfg/management/commands/rundramatiq.py +0 -24
  121. django_cfg/management/commands/rundramatiq_simulator.py +0 -22
  122. django_cfg/management/commands/task_clear.py +0 -25
  123. django_cfg/management/commands/task_status.py +0 -24
  124. django_cfg/modules/django_tasks/__init__.py +0 -29
  125. django_cfg/modules/django_tasks/dramatiq_setup.py +0 -20
  126. django_cfg/modules/django_tasks/factory.py +0 -127
  127. django_cfg/modules/django_tasks/management/commands/__init__.py +0 -0
  128. django_cfg/modules/django_tasks/management/commands/rundramatiq.py +0 -253
  129. django_cfg/modules/django_tasks/management/commands/rundramatiq_simulator.py +0 -436
  130. django_cfg/modules/django_tasks/management/commands/task_clear.py +0 -226
  131. django_cfg/modules/django_tasks/management/commands/task_status.py +0 -257
  132. django_cfg/modules/django_tasks/service.py +0 -281
  133. django_cfg/modules/django_tasks/settings.py +0 -107
  134. /django_cfg/{modules/django_tasks/management → apps/tasks/migrations}/__init__.py +0 -0
  135. {django_cfg-1.4.106.dist-info → django_cfg-1.4.108.dist-info}/WHEEL +0 -0
  136. {django_cfg-1.4.106.dist-info → django_cfg-1.4.108.dist-info}/entry_points.txt +0 -0
  137. {django_cfg-1.4.106.dist-info → django_cfg-1.4.108.dist-info}/licenses/LICENSE +0 -0
@@ -1,436 +0,0 @@
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 json
12
- import random
13
- import time
14
- from datetime import datetime, timezone
15
- from typing import Any, Dict
16
-
17
- import redis
18
- from django.core.management.base import BaseCommand, CommandError
19
-
20
- from django_cfg.modules.django_tasks import DjangoTasks
21
-
22
-
23
- class TaskSimulator:
24
- """Task data simulator for Tasks Dashboard."""
25
-
26
- def __init__(self):
27
- """Initialize the simulator."""
28
- self.tasks_service = DjangoTasks()
29
-
30
- # Get Redis client using the same logic as DjangoTasks
31
- try:
32
- redis_url = self.tasks_service.get_redis_url()
33
- if not redis_url:
34
- raise RuntimeError("No Redis URL available")
35
-
36
- # Parse URL for connection
37
- from urllib.parse import urlparse
38
- parsed = urlparse(redis_url)
39
-
40
- self.redis_client = redis.Redis(
41
- host=parsed.hostname or 'localhost',
42
- port=parsed.port or 6379,
43
- db=int(parsed.path.lstrip('/')) if parsed.path else 1,
44
- decode_responses=True
45
- )
46
-
47
- except Exception as e:
48
- raise CommandError(f"Failed to connect to Redis: {e}")
49
-
50
- # Get queue configuration
51
- try:
52
- config = self.tasks_service.get_config()
53
- self.queues = config.tasks.dramatiq.queues
54
- except Exception:
55
- # Use default queues if we can't get configuration
56
- self.queues = ['critical', 'high', 'default', 'low', 'background', 'payments', 'agents', 'knowbase']
57
-
58
- def clear_all_data(self) -> int:
59
- """
60
- Clear all test data.
61
-
62
- Returns:
63
- Number of deleted keys
64
- """
65
- keys = self.redis_client.keys("dramatiq:*")
66
- if keys:
67
- deleted = self.redis_client.delete(*keys)
68
- return deleted
69
- return 0
70
-
71
- def simulate_queues(self, pending_tasks_per_queue=None, failed_tasks_per_queue=None) -> Dict[str, Dict[str, int]]:
72
- """
73
- Simulate queues with tasks.
74
-
75
- Args:
76
- pending_tasks_per_queue: Dict[str, int] - number of pending tasks per queue
77
- failed_tasks_per_queue: Dict[str, int] - number of failed tasks per queue
78
-
79
- Returns:
80
- Dict with information about created tasks
81
- """
82
- if pending_tasks_per_queue is None:
83
- pending_tasks_per_queue = {
84
- 'critical': 2,
85
- 'high': 5,
86
- 'default': 12,
87
- 'low': 8,
88
- 'background': 15,
89
- 'payments': 3,
90
- 'agents': 7,
91
- 'knowbase': 4
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
- 'knowbase': 0
104
- }
105
-
106
- results = {}
107
-
108
- for queue_name in self.queues:
109
- queue_results = {'pending': 0, 'failed': 0}
110
-
111
- # Pending tasks
112
- pending_count = pending_tasks_per_queue.get(queue_name, 0)
113
- if pending_count > 0:
114
- queue_key = f"dramatiq:default.DQ.{queue_name}"
115
-
116
- # Add fake tasks to queue
117
- for i in range(pending_count):
118
- task_data = {
119
- "queue_name": queue_name,
120
- "actor_name": f"process_{queue_name}_task",
121
- "args": [f"task_{i}"],
122
- "kwargs": {},
123
- "options": {},
124
- "message_id": f"msg_{queue_name}_{i}_{int(time.time())}",
125
- "message_timestamp": int(time.time() * 1000)
126
- }
127
- self.redis_client.lpush(queue_key, json.dumps(task_data))
128
-
129
- queue_results['pending'] = pending_count
130
-
131
- # Failed tasks
132
- failed_count = failed_tasks_per_queue.get(queue_name, 0)
133
- if failed_count > 0:
134
- failed_key = f"dramatiq:default.DQ.{queue_name}.failed"
135
-
136
- # Add fake failed tasks
137
- for i in range(failed_count):
138
- failed_task_data = {
139
- "queue_name": queue_name,
140
- "actor_name": f"failed_{queue_name}_task",
141
- "args": [f"failed_task_{i}"],
142
- "kwargs": {},
143
- "options": {},
144
- "message_id": f"failed_msg_{queue_name}_{i}_{int(time.time())}",
145
- "message_timestamp": int(time.time() * 1000),
146
- "error": f"Simulated error for {queue_name} task {i}"
147
- }
148
- self.redis_client.lpush(failed_key, json.dumps(failed_task_data))
149
-
150
- queue_results['failed'] = failed_count
151
-
152
- if queue_results['pending'] > 0 or queue_results['failed'] > 0:
153
- results[queue_name] = queue_results
154
-
155
- return results
156
-
157
- def simulate_workers(self, worker_count=3) -> list:
158
- """
159
- Симулировать активных воркеров.
160
-
161
- Args:
162
- worker_count: Количество воркеров для симуляции
163
-
164
- Returns:
165
- Список ID созданных воркеров
166
- """
167
- worker_ids = []
168
-
169
- for i in range(worker_count):
170
- worker_id = f"worker_{i}_{int(time.time())}"
171
- worker_key = f"dramatiq:worker:{worker_id}"
172
-
173
- worker_data = {
174
- "worker_id": worker_id,
175
- "hostname": "localhost",
176
- "pid": 1000 + i,
177
- "queues": self.queues,
178
- "started_at": datetime.now(timezone.utc).isoformat(),
179
- "last_heartbeat": datetime.now(timezone.utc).isoformat(),
180
- "status": "active"
181
- }
182
-
183
- # Устанавливаем данные воркера с TTL
184
- self.redis_client.setex(
185
- worker_key,
186
- 300, # 5 минут TTL
187
- json.dumps(worker_data)
188
- )
189
-
190
- worker_ids.append(worker_id)
191
-
192
- return worker_ids
193
-
194
- def simulate_task_statistics(self) -> Dict[str, Any]:
195
- """
196
- Симулировать статистику задач.
197
-
198
- Returns:
199
- Созданная статистика
200
- """
201
- stats_data = {
202
- "total_processed": random.randint(1000, 2000),
203
- "total_failed": random.randint(30, 80),
204
- "total_retried": random.randint(15, 40),
205
- "processing_time_avg": round(random.uniform(1.5, 4.0), 2),
206
- "last_updated": datetime.now(timezone.utc).isoformat()
207
- }
208
-
209
- stats_key = "dramatiq:stats"
210
- self.redis_client.setex(stats_key, 3600, json.dumps(stats_data))
211
-
212
- return stats_data
213
-
214
- def run_simulation(self, workers=3, clear_first=True) -> Dict[str, Any]:
215
- """
216
- Запустить полную симуляцию.
217
-
218
- Args:
219
- workers: Количество воркеров
220
- clear_first: Очистить данные перед симуляцией
221
-
222
- Returns:
223
- Результаты симуляции
224
- """
225
- results = {
226
- 'cleared_keys': 0,
227
- 'queues': {},
228
- 'workers': [],
229
- 'statistics': {}
230
- }
231
-
232
- if clear_first:
233
- results['cleared_keys'] = self.clear_all_data()
234
-
235
- results['queues'] = self.simulate_queues()
236
- results['workers'] = self.simulate_workers(workers)
237
- results['statistics'] = self.simulate_task_statistics()
238
-
239
- return results
240
-
241
- def get_redis_summary(self) -> Dict[str, Any]:
242
- """Получить сводку по данным в Redis."""
243
- summary = {
244
- 'total_keys': 0,
245
- 'queues': {},
246
- 'workers': 0,
247
- 'statistics': None
248
- }
249
-
250
- # Подсчитываем все ключи
251
- all_keys = self.redis_client.keys("dramatiq:*")
252
- summary['total_keys'] = len(all_keys)
253
-
254
- # Анализируем очереди
255
- for queue_name in self.queues:
256
- pending_key = f"dramatiq:default.DQ.{queue_name}"
257
- failed_key = f"dramatiq:default.DQ.{queue_name}.failed"
258
-
259
- pending = self.redis_client.llen(pending_key)
260
- failed = self.redis_client.llen(failed_key)
261
-
262
- if pending > 0 or failed > 0:
263
- summary['queues'][queue_name] = {
264
- 'pending': pending,
265
- 'failed': failed
266
- }
267
-
268
- # Подсчитываем воркеров
269
- worker_keys = self.redis_client.keys("dramatiq:worker:*")
270
- summary['workers'] = len(worker_keys)
271
-
272
- # Получаем статистику
273
- stats_key = "dramatiq:stats"
274
- if self.redis_client.exists(stats_key):
275
- try:
276
- stats_data = self.redis_client.get(stats_key)
277
- summary['statistics'] = json.loads(stats_data)
278
- except:
279
- pass
280
-
281
- return summary
282
-
283
-
284
- class Command(BaseCommand):
285
- """Django management command для симуляции Dramatiq данных."""
286
-
287
- # Web execution metadata
288
- web_executable = False
289
- requires_input = False
290
- is_destructive = False
291
-
292
- help = 'Simulate Dramatiq tasks and workers for dashboard testing'
293
-
294
- def add_arguments(self, parser):
295
- """Добавить аргументы команды."""
296
- parser.add_argument(
297
- '--workers',
298
- type=int,
299
- default=3,
300
- help='Number of workers to simulate (default: 3)'
301
- )
302
-
303
- parser.add_argument(
304
- '--no-clear',
305
- action='store_true',
306
- help='Do not clear existing data before simulation'
307
- )
308
-
309
- parser.add_argument(
310
- '--clear-only',
311
- action='store_true',
312
- help='Only clear data, do not simulate'
313
- )
314
-
315
- parser.add_argument(
316
- '--show-keys',
317
- action='store_true',
318
- help='Show Redis keys after operation'
319
- )
320
-
321
- parser.add_argument(
322
- '--summary',
323
- action='store_true',
324
- help='Show summary of current Redis data'
325
- )
326
-
327
- def handle(self, *args, **options):
328
- """Выполнить команду."""
329
- try:
330
- simulator = TaskSimulator()
331
-
332
- # Показать только сводку
333
- if options['summary']:
334
- self.show_summary(simulator)
335
- return
336
-
337
- # Только очистка
338
- if options['clear_only']:
339
- self.stdout.write("🧹 Clearing all test data...")
340
- cleared = simulator.clear_all_data()
341
- self.stdout.write(
342
- self.style.SUCCESS(f"✅ Cleared {cleared} Redis keys")
343
- )
344
- return
345
-
346
- # Полная симуляция
347
- self.stdout.write("🎭 Starting Dramatiq Task Simulation")
348
- self.stdout.write("=" * 50)
349
-
350
- results = simulator.run_simulation(
351
- workers=options['workers'],
352
- clear_first=not options['no_clear']
353
- )
354
-
355
- # Показываем результаты
356
- if results['cleared_keys'] > 0:
357
- self.stdout.write(f"🧹 Cleared {results['cleared_keys']} existing keys")
358
-
359
- self.stdout.write("📋 Created queues:")
360
- total_pending = 0
361
- total_failed = 0
362
-
363
- for queue_name, counts in results['queues'].items():
364
- pending = counts['pending']
365
- failed = counts['failed']
366
- total_pending += pending
367
- total_failed += failed
368
-
369
- self.stdout.write(f" {queue_name}: {pending} pending, {failed} failed")
370
-
371
- self.stdout.write(f"👷 Created {len(results['workers'])} workers")
372
- self.stdout.write("📊 Added task statistics")
373
-
374
- self.stdout.write("=" * 50)
375
- self.stdout.write(self.style.SUCCESS("✅ Simulation completed!"))
376
-
377
- self.stdout.write("\n📊 Summary:")
378
- active_queues = len(results['queues'])
379
- self.stdout.write(f" Active Queues: {active_queues}")
380
- self.stdout.write(f" Active Workers: {len(results['workers'])}")
381
- self.stdout.write(f" Pending Tasks: {total_pending}")
382
- self.stdout.write(f" Failed Tasks: {total_failed}")
383
-
384
- self.stdout.write("\n🌐 Dashboard URL: http://localhost:8000/cfg/admin/django_cfg_tasks/admin/dashboard/")
385
-
386
- # Показать ключи если запрошено
387
- if options['show_keys']:
388
- self.show_redis_keys(simulator)
389
-
390
- except Exception as e:
391
- raise CommandError(f"Simulation failed: {e}")
392
-
393
- def show_summary(self, simulator: TaskSimulator):
394
- """Показать сводку текущих данных."""
395
- self.stdout.write("📊 Current Redis Data Summary")
396
- self.stdout.write("=" * 40)
397
-
398
- summary = simulator.get_redis_summary()
399
-
400
- self.stdout.write(f"Total Redis keys: {summary['total_keys']}")
401
- self.stdout.write(f"Active workers: {summary['workers']}")
402
-
403
- if summary['queues']:
404
- self.stdout.write("\nQueues:")
405
- for queue_name, counts in summary['queues'].items():
406
- self.stdout.write(f" {queue_name}: {counts['pending']} pending, {counts['failed']} failed")
407
- else:
408
- self.stdout.write("\nNo active queues found")
409
-
410
- if summary['statistics']:
411
- stats = summary['statistics']
412
- self.stdout.write("\nStatistics:")
413
- self.stdout.write(f" Total processed: {stats.get('total_processed', 'N/A')}")
414
- self.stdout.write(f" Total failed: {stats.get('total_failed', 'N/A')}")
415
- self.stdout.write(f" Avg processing time: {stats.get('processing_time_avg', 'N/A')}s")
416
-
417
- def show_redis_keys(self, simulator: TaskSimulator):
418
- """Показать все Redis ключи."""
419
- self.stdout.write("\n🔍 Redis Keys:")
420
- keys = simulator.redis_client.keys("dramatiq:*")
421
-
422
- if not keys:
423
- self.stdout.write(" No Dramatiq keys found")
424
- return
425
-
426
- for key in sorted(keys):
427
- key_type = simulator.redis_client.type(key)
428
- if key_type == 'list':
429
- length = simulator.redis_client.llen(key)
430
- self.stdout.write(f" {key} (list): {length} items")
431
- elif key_type == 'string':
432
- ttl = simulator.redis_client.ttl(key)
433
- ttl_str = f"TTL {ttl}s" if ttl > 0 else "no TTL"
434
- self.stdout.write(f" {key} (string): {ttl_str}")
435
- else:
436
- self.stdout.write(f" {key} ({key_type})")
@@ -1,226 +0,0 @@
1
- """
2
- Django management command for clearing task queues.
3
-
4
- This command provides utilities for clearing failed tasks,
5
- specific queues, or all tasks from the Dramatiq system.
6
- """
7
-
8
- from typing import List
9
-
10
- from django.core.management.base import BaseCommand, CommandError
11
-
12
- from django_cfg.modules.django_logging import get_logger
13
-
14
- logger = get_logger('task_clear')
15
-
16
-
17
- class Command(BaseCommand):
18
- """
19
- Clear tasks from Dramatiq queues.
20
-
21
- Provides options to:
22
- - Clear all tasks from specific queues
23
- - Clear only failed tasks
24
- - Clear all tasks from all queues
25
- """
26
-
27
- # Web execution metadata
28
- web_executable = False
29
- requires_input = True
30
- is_destructive = True
31
-
32
- help = "Clear tasks from Dramatiq queues"
33
-
34
- def add_arguments(self, parser):
35
- """Add command line arguments."""
36
- parser.add_argument(
37
- "--queue",
38
- type=str,
39
- help="Specific queue to clear (default: all queues)",
40
- )
41
- parser.add_argument(
42
- "--failed-only",
43
- action="store_true",
44
- help="Clear only failed tasks",
45
- )
46
- parser.add_argument(
47
- "--confirm",
48
- action="store_true",
49
- help="Skip confirmation prompt",
50
- )
51
- parser.add_argument(
52
- "--dry-run",
53
- action="store_true",
54
- help="Show what would be cleared without actually clearing",
55
- )
56
-
57
- def handle(self, *args, **options):
58
- """Handle the command execution."""
59
- logger.info("Starting task_clear command")
60
-
61
- try:
62
- # Import here to avoid issues if dramatiq is not installed
63
- from django_cfg.modules.django_tasks import get_task_service
64
-
65
- # Get task service
66
- task_service = get_task_service()
67
-
68
- # Check if task system is enabled
69
- if not task_service.is_enabled():
70
- raise CommandError(
71
- "Task system is not enabled. "
72
- "Please configure 'tasks' in your Django-CFG configuration."
73
- )
74
-
75
- # Get task manager
76
- manager = task_service.manager
77
- if not manager:
78
- raise CommandError("Task manager not available")
79
-
80
- # Get configuration
81
- config = task_service.config
82
- if not config:
83
- raise CommandError("Task configuration not available")
84
-
85
- # Determine queues to clear
86
- if options.get("queue"):
87
- queues_to_clear = [options["queue"]]
88
- # Validate queue exists
89
- if options["queue"] not in config.get_effective_queues():
90
- self.stdout.write(
91
- self.style.WARNING(
92
- f"Queue '{options['queue']}' not in configured queues: "
93
- f"{', '.join(config.get_effective_queues())}"
94
- )
95
- )
96
- else:
97
- queues_to_clear = config.get_effective_queues()
98
-
99
- # Show what will be cleared
100
- self._show_clear_plan(queues_to_clear, options)
101
-
102
- if options["dry_run"]:
103
- self.stdout.write(
104
- self.style.SUCCESS("Dry run completed - no tasks were cleared")
105
- )
106
- return
107
-
108
- # Confirm action
109
- if not options["confirm"]:
110
- if not self._confirm_clear(queues_to_clear, options):
111
- self.stdout.write("Operation cancelled")
112
- return
113
-
114
- # Perform clearing
115
- self._clear_queues(manager, queues_to_clear, options)
116
-
117
- except ImportError:
118
- raise CommandError(
119
- "Dramatiq dependencies not installed. "
120
- "Install with: pip install django-cfg[tasks]"
121
- )
122
- except Exception as e:
123
- logger.exception("Failed to clear tasks")
124
- raise CommandError(f"Failed to clear tasks: {e}")
125
-
126
- def _show_clear_plan(self, queues: List[str], options):
127
- """Show what will be cleared."""
128
- self.stdout.write(
129
- self.style.SUCCESS("=== Clear Plan ===")
130
- )
131
-
132
- if options.get("failed_only"):
133
- self.stdout.write("Action: Clear FAILED tasks only")
134
- else:
135
- self.stdout.write("Action: Clear ALL tasks")
136
-
137
- self.stdout.write(f"Queues: {', '.join(queues)}")
138
-
139
- # Get current queue statistics
140
- try:
141
- from django_cfg.modules.django_tasks import get_task_service
142
- task_service = get_task_service()
143
- manager = task_service.manager
144
-
145
- if manager:
146
- queue_stats = manager.get_queue_stats()
147
-
148
- self.stdout.write("\nCurrent Queue Statistics:")
149
- for stat in queue_stats:
150
- if stat["name"] in queues:
151
- name = stat["name"]
152
- pending = stat.get("pending", 0)
153
- failed = stat.get("failed", 0)
154
-
155
- if options.get("failed_only"):
156
- self.stdout.write(f" {name}: {failed} failed tasks")
157
- else:
158
- total = pending + stat.get("running", 0) + failed
159
- self.stdout.write(f" {name}: {total} total tasks")
160
- except Exception as e:
161
- self.stdout.write(f"Could not get queue statistics: {e}")
162
-
163
- self.stdout.write()
164
-
165
- def _confirm_clear(self, queues: List[str], options) -> bool:
166
- """Confirm the clear operation with user."""
167
- if options.get("failed_only"):
168
- action = "clear FAILED tasks"
169
- else:
170
- action = "clear ALL tasks"
171
-
172
- queue_list = ", ".join(queues)
173
-
174
- self.stdout.write(
175
- self.style.WARNING(
176
- f"This will {action} from queues: {queue_list}"
177
- )
178
- )
179
-
180
- response = input("Are you sure? [y/N]: ").lower().strip()
181
- return response in ["y", "yes"]
182
-
183
- def _clear_queues(self, manager, queues: List[str], options):
184
- """Clear the specified queues."""
185
- cleared_count = 0
186
-
187
- self.stdout.write(
188
- self.style.SUCCESS("Starting queue clearing...")
189
- )
190
-
191
- for queue_name in queues:
192
- try:
193
- self.stdout.write(f"Clearing queue: {queue_name}")
194
-
195
- if options.get("failed_only"):
196
- # Clear only failed tasks
197
- # TODO: Implement failed task clearing
198
- # This would require specific Dramatiq broker methods
199
- count = 0 # Placeholder
200
- self.stdout.write(f" Cleared {count} failed tasks")
201
- else:
202
- # Clear all tasks
203
- success = manager.clear_queue(queue_name)
204
- if success:
205
- # TODO: Get actual count of cleared tasks
206
- count = 0 # Placeholder
207
- self.stdout.write(f" Cleared {count} tasks")
208
- cleared_count += count
209
- else:
210
- self.stdout.write(
211
- self.style.ERROR(f" Failed to clear queue: {queue_name}")
212
- )
213
-
214
- except Exception as e:
215
- self.stdout.write(
216
- self.style.ERROR(f" Error clearing queue {queue_name}: {e}")
217
- )
218
-
219
- if cleared_count > 0:
220
- self.stdout.write(
221
- self.style.SUCCESS(f"Successfully cleared {cleared_count} tasks")
222
- )
223
- else:
224
- self.stdout.write(
225
- self.style.WARNING("No tasks were cleared")
226
- )