django-cfg 1.4.120__py3-none-any.whl → 1.5.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of django-cfg might be problematic. Click here for more details.
- django_cfg/__init__.py +8 -4
- django_cfg/apps/centrifugo/admin/centrifugo_log.py +33 -71
- django_cfg/apps/dashboard/TRANSACTION_FIX.md +73 -0
- django_cfg/apps/dashboard/serializers/__init__.py +0 -12
- django_cfg/apps/dashboard/serializers/activity.py +1 -1
- django_cfg/apps/dashboard/services/__init__.py +0 -2
- django_cfg/apps/dashboard/services/charts_service.py +4 -3
- django_cfg/apps/dashboard/services/statistics_service.py +11 -2
- django_cfg/apps/dashboard/services/system_health_service.py +64 -106
- django_cfg/apps/dashboard/urls.py +0 -2
- django_cfg/apps/dashboard/views/__init__.py +0 -2
- django_cfg/apps/dashboard/views/commands_views.py +3 -6
- django_cfg/apps/dashboard/views/overview_views.py +14 -13
- django_cfg/apps/grpc/__init__.py +9 -0
- django_cfg/apps/grpc/admin/__init__.py +11 -0
- django_cfg/apps/{tasks → grpc}/admin/config.py +32 -41
- django_cfg/apps/grpc/admin/grpc_request_log.py +252 -0
- django_cfg/apps/grpc/apps.py +28 -0
- django_cfg/apps/grpc/auth/__init__.py +9 -0
- django_cfg/apps/grpc/auth/jwt_auth.py +295 -0
- django_cfg/apps/grpc/interceptors/__init__.py +19 -0
- django_cfg/apps/grpc/interceptors/errors.py +241 -0
- django_cfg/apps/grpc/interceptors/logging.py +270 -0
- django_cfg/apps/grpc/interceptors/metrics.py +306 -0
- django_cfg/apps/grpc/interceptors/request_logger.py +515 -0
- django_cfg/apps/grpc/management/__init__.py +1 -0
- django_cfg/apps/grpc/management/commands/rungrpc.py +302 -0
- django_cfg/apps/grpc/managers/__init__.py +10 -0
- django_cfg/apps/grpc/managers/grpc_request_log.py +310 -0
- django_cfg/apps/grpc/migrations/0001_initial.py +69 -0
- django_cfg/apps/grpc/migrations/0002_rename_django_cfg__service_4c4a8e_idx_django_cfg__service_584308_idx_and_more.py +38 -0
- django_cfg/apps/grpc/models/__init__.py +9 -0
- django_cfg/apps/grpc/models/grpc_request_log.py +219 -0
- django_cfg/apps/grpc/serializers/__init__.py +23 -0
- django_cfg/apps/grpc/serializers/health.py +18 -0
- django_cfg/apps/grpc/serializers/requests.py +18 -0
- django_cfg/apps/grpc/serializers/services.py +50 -0
- django_cfg/apps/grpc/serializers/stats.py +22 -0
- django_cfg/apps/grpc/services/__init__.py +16 -0
- django_cfg/apps/grpc/services/base.py +375 -0
- django_cfg/apps/grpc/services/discovery.py +415 -0
- django_cfg/apps/grpc/urls.py +23 -0
- django_cfg/apps/grpc/utils/__init__.py +13 -0
- django_cfg/apps/grpc/utils/proto_gen.py +423 -0
- django_cfg/apps/grpc/views/__init__.py +9 -0
- django_cfg/apps/grpc/views/monitoring.py +497 -0
- django_cfg/apps/knowbase/apps.py +2 -2
- django_cfg/apps/maintenance/admin/api_key_admin.py +7 -9
- django_cfg/apps/maintenance/admin/site_admin.py +5 -4
- django_cfg/apps/newsletter/admin/newsletter_admin.py +12 -11
- django_cfg/apps/payments/admin/balance_admin.py +26 -36
- django_cfg/apps/payments/admin/payment_admin.py +65 -85
- django_cfg/apps/payments/admin/withdrawal_admin.py +65 -100
- django_cfg/apps/rq/__init__.py +9 -0
- django_cfg/apps/rq/apps.py +80 -0
- django_cfg/apps/rq/management/__init__.py +1 -0
- django_cfg/apps/rq/management/commands/__init__.py +1 -0
- django_cfg/apps/rq/management/commands/rqscheduler.py +31 -0
- django_cfg/apps/rq/management/commands/rqstats.py +33 -0
- django_cfg/apps/rq/management/commands/rqworker.py +31 -0
- django_cfg/apps/rq/management/commands/rqworker_pool.py +27 -0
- django_cfg/apps/rq/serializers/__init__.py +40 -0
- django_cfg/apps/rq/serializers/health.py +60 -0
- django_cfg/apps/rq/serializers/job.py +100 -0
- django_cfg/apps/rq/serializers/queue.py +80 -0
- django_cfg/apps/rq/serializers/schedule.py +178 -0
- django_cfg/apps/rq/serializers/testing.py +139 -0
- django_cfg/apps/rq/serializers/worker.py +58 -0
- django_cfg/apps/rq/services/__init__.py +25 -0
- django_cfg/apps/rq/services/config_helper.py +233 -0
- django_cfg/apps/rq/services/models/README.md +417 -0
- django_cfg/apps/rq/services/models/__init__.py +30 -0
- django_cfg/apps/rq/services/models/event.py +123 -0
- django_cfg/apps/rq/services/models/job.py +99 -0
- django_cfg/apps/rq/services/models/queue.py +92 -0
- django_cfg/apps/rq/services/models/worker.py +104 -0
- django_cfg/apps/rq/services/rq_converters.py +183 -0
- django_cfg/apps/rq/tasks/__init__.py +23 -0
- django_cfg/apps/rq/tasks/demo_tasks.py +284 -0
- django_cfg/apps/rq/urls.py +54 -0
- django_cfg/apps/rq/views/__init__.py +19 -0
- django_cfg/apps/rq/views/jobs.py +882 -0
- django_cfg/apps/rq/views/monitoring.py +248 -0
- django_cfg/apps/rq/views/queues.py +261 -0
- django_cfg/apps/rq/views/schedule.py +400 -0
- django_cfg/apps/rq/views/testing.py +761 -0
- django_cfg/apps/rq/views/workers.py +195 -0
- django_cfg/apps/urls.py +13 -8
- django_cfg/config.py +106 -0
- django_cfg/core/base/config_model.py +16 -26
- django_cfg/core/builders/apps_builder.py +7 -11
- django_cfg/core/generation/integration_generators/__init__.py +3 -6
- django_cfg/core/generation/integration_generators/django_rq.py +80 -0
- django_cfg/core/generation/integration_generators/grpc_generator.py +318 -0
- django_cfg/core/generation/orchestrator.py +15 -15
- django_cfg/core/integration/display/startup.py +6 -20
- django_cfg/mixins/__init__.py +2 -0
- django_cfg/mixins/superadmin_api.py +59 -0
- django_cfg/models/__init__.py +3 -3
- django_cfg/models/api/grpc/__init__.py +59 -0
- django_cfg/models/api/grpc/config.py +364 -0
- django_cfg/models/django/__init__.py +3 -3
- django_cfg/models/django/django_rq.py +621 -0
- django_cfg/models/django/revolution_legacy.py +1 -1
- django_cfg/modules/base.py +19 -6
- django_cfg/modules/django_admin/base/pydantic_admin.py +2 -2
- django_cfg/modules/django_admin/config/background_task_config.py +4 -4
- django_cfg/modules/django_admin/utils/__init__.py +41 -3
- django_cfg/modules/django_admin/utils/badges/__init__.py +13 -0
- django_cfg/modules/django_admin/utils/{badges.py → badges/status_badges.py} +3 -3
- django_cfg/modules/django_admin/utils/displays/__init__.py +13 -0
- django_cfg/modules/django_admin/utils/{displays.py → displays/data_displays.py} +2 -2
- django_cfg/modules/django_admin/utils/html/__init__.py +26 -0
- django_cfg/modules/django_admin/utils/html/badges.py +47 -0
- django_cfg/modules/django_admin/utils/html/base.py +167 -0
- django_cfg/modules/django_admin/utils/html/code.py +87 -0
- django_cfg/modules/django_admin/utils/html/composition.py +205 -0
- django_cfg/modules/django_admin/utils/html/formatting.py +231 -0
- django_cfg/modules/django_admin/utils/html/keyvalue.py +219 -0
- django_cfg/modules/django_admin/utils/html/markdown_integration.py +108 -0
- django_cfg/modules/django_admin/utils/html/progress.py +127 -0
- django_cfg/modules/django_admin/utils/html_builder.py +55 -408
- django_cfg/modules/django_admin/utils/markdown/__init__.py +21 -0
- django_cfg/modules/django_unfold/navigation.py +21 -18
- django_cfg/pyproject.toml +4 -6
- django_cfg/registry/core.py +4 -7
- django_cfg/registry/modules.py +6 -0
- django_cfg/static/frontend/admin.zip +0 -0
- django_cfg/templates/admin/constance/includes/results_list.html +73 -0
- django_cfg/templates/admin/index.html +187 -62
- django_cfg/templatetags/django_cfg.py +61 -1
- {django_cfg-1.4.120.dist-info → django_cfg-1.5.2.dist-info}/METADATA +12 -4
- {django_cfg-1.4.120.dist-info → django_cfg-1.5.2.dist-info}/RECORD +140 -96
- django_cfg/apps/dashboard/permissions.py +0 -48
- django_cfg/apps/dashboard/serializers/django_q2.py +0 -50
- django_cfg/apps/dashboard/services/django_q2_service.py +0 -159
- django_cfg/apps/dashboard/views/django_q2_views.py +0 -79
- django_cfg/apps/tasks/__init__.py +0 -64
- django_cfg/apps/tasks/admin/__init__.py +0 -4
- django_cfg/apps/tasks/admin/task_log.py +0 -265
- django_cfg/apps/tasks/apps.py +0 -15
- django_cfg/apps/tasks/filters/__init__.py +0 -10
- django_cfg/apps/tasks/filters/task_log.py +0 -121
- django_cfg/apps/tasks/migrations/0001_initial.py +0 -196
- django_cfg/apps/tasks/migrations/0002_delete_tasklog.py +0 -16
- django_cfg/apps/tasks/models/__init__.py +0 -4
- django_cfg/apps/tasks/models/task_log.py +0 -246
- django_cfg/apps/tasks/serializers/__init__.py +0 -28
- django_cfg/apps/tasks/serializers/task_log.py +0 -249
- django_cfg/apps/tasks/services/__init__.py +0 -10
- django_cfg/apps/tasks/services/client/__init__.py +0 -7
- django_cfg/apps/tasks/services/client/client.py +0 -234
- django_cfg/apps/tasks/services/config_helper.py +0 -63
- django_cfg/apps/tasks/services/sync.py +0 -204
- django_cfg/apps/tasks/urls.py +0 -16
- django_cfg/apps/tasks/views/__init__.py +0 -10
- django_cfg/apps/tasks/views/task_log.py +0 -41
- django_cfg/apps/tasks/views/task_log_base.py +0 -41
- django_cfg/apps/tasks/views/task_log_overview.py +0 -100
- django_cfg/apps/tasks/views/task_log_related.py +0 -41
- django_cfg/apps/tasks/views/task_log_stats.py +0 -91
- django_cfg/apps/tasks/views/task_log_timeline.py +0 -81
- django_cfg/core/generation/integration_generators/django_q2.py +0 -133
- django_cfg/core/generation/integration_generators/tasks.py +0 -88
- django_cfg/models/django/django_q2.py +0 -514
- django_cfg/models/tasks/__init__.py +0 -49
- django_cfg/models/tasks/backends.py +0 -122
- django_cfg/models/tasks/config.py +0 -209
- django_cfg/models/tasks/utils.py +0 -162
- django_cfg/modules/django_admin/utils/CODE_BLOCK_DOCS.md +0 -396
- django_cfg/modules/django_q2/README.md +0 -140
- django_cfg/modules/django_q2/__init__.py +0 -8
- django_cfg/modules/django_q2/apps.py +0 -107
- django_cfg/modules/django_q2/management/commands/__init__.py +0 -0
- django_cfg/modules/django_q2/management/commands/sync_django_q_schedules.py +0 -74
- /django_cfg/apps/{tasks/migrations → grpc/management/commands}/__init__.py +0 -0
- /django_cfg/{modules/django_q2/management → apps/grpc/migrations}/__init__.py +0 -0
- /django_cfg/modules/django_admin/utils/{mermaid_plugin.py → markdown/mermaid_plugin.py} +0 -0
- /django_cfg/modules/django_admin/utils/{markdown_renderer.py → markdown/renderer.py} +0 -0
- {django_cfg-1.4.120.dist-info → django_cfg-1.5.2.dist-info}/WHEEL +0 -0
- {django_cfg-1.4.120.dist-info → django_cfg-1.5.2.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.4.120.dist-info → django_cfg-1.5.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Django-Q2 Task Serializers
|
|
3
|
-
|
|
4
|
-
Serializers for displaying Django-Q2 scheduled tasks and task history.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
from rest_framework import serializers
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class DjangoQ2ScheduleSerializer(serializers.Serializer):
|
|
11
|
-
"""Serializer for Django-Q2 scheduled tasks."""
|
|
12
|
-
|
|
13
|
-
id = serializers.IntegerField(read_only=True)
|
|
14
|
-
name = serializers.CharField(max_length=255)
|
|
15
|
-
func = serializers.CharField(max_length=512)
|
|
16
|
-
schedule_type = serializers.CharField(max_length=24)
|
|
17
|
-
repeats = serializers.IntegerField()
|
|
18
|
-
next_run = serializers.DateTimeField(allow_null=True)
|
|
19
|
-
last_run = serializers.DateTimeField(allow_null=True)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
class DjangoQ2TaskSerializer(serializers.Serializer):
|
|
23
|
-
"""Serializer for Django-Q2 task execution history."""
|
|
24
|
-
|
|
25
|
-
id = serializers.CharField(max_length=32)
|
|
26
|
-
name = serializers.CharField(max_length=255)
|
|
27
|
-
func = serializers.CharField(max_length=512)
|
|
28
|
-
started = serializers.DateTimeField()
|
|
29
|
-
stopped = serializers.DateTimeField(allow_null=True)
|
|
30
|
-
success = serializers.BooleanField()
|
|
31
|
-
result = serializers.CharField(allow_null=True)
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
class DjangoQ2StatusSerializer(serializers.Serializer):
|
|
35
|
-
"""Serializer for Django-Q2 cluster status."""
|
|
36
|
-
|
|
37
|
-
cluster_running = serializers.BooleanField()
|
|
38
|
-
total_schedules = serializers.IntegerField()
|
|
39
|
-
active_schedules = serializers.IntegerField()
|
|
40
|
-
recent_tasks = serializers.IntegerField()
|
|
41
|
-
successful_tasks = serializers.IntegerField()
|
|
42
|
-
failed_tasks = serializers.IntegerField()
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
class DjangoQ2SummarySerializer(serializers.Serializer):
|
|
46
|
-
"""Summary serializer for Django-Q2 dashboard."""
|
|
47
|
-
|
|
48
|
-
status = DjangoQ2StatusSerializer()
|
|
49
|
-
schedules = DjangoQ2ScheduleSerializer(many=True)
|
|
50
|
-
recent_tasks = DjangoQ2TaskSerializer(many=True)
|
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Django-Q2 Service
|
|
3
|
-
|
|
4
|
-
Business logic for collecting Django-Q2 task data.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
from typing import Dict, List, Any
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class DjangoQ2Service:
|
|
11
|
-
"""Service for aggregating Django-Q2 task information."""
|
|
12
|
-
|
|
13
|
-
@staticmethod
|
|
14
|
-
def get_schedules() -> List[Dict[str, Any]]:
|
|
15
|
-
"""
|
|
16
|
-
Get all scheduled tasks from Django-Q2.
|
|
17
|
-
|
|
18
|
-
Returns:
|
|
19
|
-
List of scheduled tasks with their configurations
|
|
20
|
-
"""
|
|
21
|
-
try:
|
|
22
|
-
from django_q.models import Schedule
|
|
23
|
-
|
|
24
|
-
schedules = Schedule.objects.all().order_by('-next_run')
|
|
25
|
-
return [
|
|
26
|
-
{
|
|
27
|
-
'id': schedule.id,
|
|
28
|
-
'name': schedule.name,
|
|
29
|
-
'func': schedule.func,
|
|
30
|
-
'schedule_type': schedule.schedule_type,
|
|
31
|
-
'repeats': schedule.repeats,
|
|
32
|
-
'next_run': schedule.next_run,
|
|
33
|
-
'last_run': getattr(schedule, 'last_run', None),
|
|
34
|
-
}
|
|
35
|
-
for schedule in schedules
|
|
36
|
-
]
|
|
37
|
-
except ImportError:
|
|
38
|
-
# django-q2 not installed
|
|
39
|
-
return []
|
|
40
|
-
except Exception:
|
|
41
|
-
# Database error or table doesn't exist yet
|
|
42
|
-
return []
|
|
43
|
-
|
|
44
|
-
@staticmethod
|
|
45
|
-
def get_recent_tasks(limit: int = 20) -> List[Dict[str, Any]]:
|
|
46
|
-
"""
|
|
47
|
-
Get recent task executions from Django-Q2.
|
|
48
|
-
|
|
49
|
-
Args:
|
|
50
|
-
limit: Maximum number of tasks to return
|
|
51
|
-
|
|
52
|
-
Returns:
|
|
53
|
-
List of recent task executions with their results
|
|
54
|
-
"""
|
|
55
|
-
try:
|
|
56
|
-
from django_q.models import Task
|
|
57
|
-
|
|
58
|
-
tasks = Task.objects.all().order_by('-started')[:limit]
|
|
59
|
-
return [
|
|
60
|
-
{
|
|
61
|
-
'id': task.id,
|
|
62
|
-
'name': task.name,
|
|
63
|
-
'func': task.func,
|
|
64
|
-
'started': task.started,
|
|
65
|
-
'stopped': task.stopped,
|
|
66
|
-
'success': task.success,
|
|
67
|
-
'result': str(task.result) if task.result else None,
|
|
68
|
-
}
|
|
69
|
-
for task in tasks
|
|
70
|
-
]
|
|
71
|
-
except ImportError:
|
|
72
|
-
# django-q2 not installed
|
|
73
|
-
return []
|
|
74
|
-
except Exception:
|
|
75
|
-
# Database error or table doesn't exist yet
|
|
76
|
-
return []
|
|
77
|
-
|
|
78
|
-
@staticmethod
|
|
79
|
-
def get_cluster_status() -> Dict[str, Any]:
|
|
80
|
-
"""
|
|
81
|
-
Get Django-Q2 cluster status.
|
|
82
|
-
|
|
83
|
-
Returns:
|
|
84
|
-
Dictionary with cluster status information
|
|
85
|
-
"""
|
|
86
|
-
try:
|
|
87
|
-
from django_q.models import Schedule, Task
|
|
88
|
-
from django_q.cluster import Cluster
|
|
89
|
-
|
|
90
|
-
# Count schedules
|
|
91
|
-
total_schedules = Schedule.objects.count()
|
|
92
|
-
active_schedules = Schedule.objects.filter(repeats__gt=0).count()
|
|
93
|
-
|
|
94
|
-
# Count recent tasks (last 24 hours)
|
|
95
|
-
from django.utils import timezone
|
|
96
|
-
from datetime import timedelta
|
|
97
|
-
|
|
98
|
-
last_24h = timezone.now() - timedelta(hours=24)
|
|
99
|
-
recent_tasks = Task.objects.filter(started__gte=last_24h).count()
|
|
100
|
-
successful_tasks = Task.objects.filter(
|
|
101
|
-
started__gte=last_24h, success=True
|
|
102
|
-
).count()
|
|
103
|
-
failed_tasks = Task.objects.filter(
|
|
104
|
-
started__gte=last_24h, success=False
|
|
105
|
-
).count()
|
|
106
|
-
|
|
107
|
-
# Check if cluster is running
|
|
108
|
-
cluster_running = False
|
|
109
|
-
try:
|
|
110
|
-
# Check for recent task activity as proxy for cluster status
|
|
111
|
-
recent_task = Task.objects.filter(
|
|
112
|
-
started__gte=timezone.now() - timedelta(minutes=5)
|
|
113
|
-
).exists()
|
|
114
|
-
cluster_running = recent_task
|
|
115
|
-
except Exception:
|
|
116
|
-
pass
|
|
117
|
-
|
|
118
|
-
return {
|
|
119
|
-
'cluster_running': cluster_running,
|
|
120
|
-
'total_schedules': total_schedules,
|
|
121
|
-
'active_schedules': active_schedules,
|
|
122
|
-
'recent_tasks': recent_tasks,
|
|
123
|
-
'successful_tasks': successful_tasks,
|
|
124
|
-
'failed_tasks': failed_tasks,
|
|
125
|
-
}
|
|
126
|
-
except ImportError:
|
|
127
|
-
# django-q2 not installed
|
|
128
|
-
return {
|
|
129
|
-
'cluster_running': False,
|
|
130
|
-
'total_schedules': 0,
|
|
131
|
-
'active_schedules': 0,
|
|
132
|
-
'recent_tasks': 0,
|
|
133
|
-
'successful_tasks': 0,
|
|
134
|
-
'failed_tasks': 0,
|
|
135
|
-
}
|
|
136
|
-
except Exception:
|
|
137
|
-
# Database error or table doesn't exist yet
|
|
138
|
-
return {
|
|
139
|
-
'cluster_running': False,
|
|
140
|
-
'total_schedules': 0,
|
|
141
|
-
'active_schedules': 0,
|
|
142
|
-
'recent_tasks': 0,
|
|
143
|
-
'successful_tasks': 0,
|
|
144
|
-
'failed_tasks': 0,
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
@classmethod
|
|
148
|
-
def get_summary(cls) -> Dict[str, Any]:
|
|
149
|
-
"""
|
|
150
|
-
Get complete Django-Q2 summary for dashboard.
|
|
151
|
-
|
|
152
|
-
Returns:
|
|
153
|
-
Dictionary with status, schedules, and recent tasks
|
|
154
|
-
"""
|
|
155
|
-
return {
|
|
156
|
-
'status': cls.get_cluster_status(),
|
|
157
|
-
'schedules': cls.get_schedules(),
|
|
158
|
-
'recent_tasks': cls.get_recent_tasks(limit=10),
|
|
159
|
-
}
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Django-Q2 ViewSet
|
|
3
|
-
|
|
4
|
-
API endpoints for Django-Q2 task monitoring.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
from rest_framework import viewsets, status
|
|
8
|
-
from rest_framework.decorators import action
|
|
9
|
-
from rest_framework.response import Response
|
|
10
|
-
from rest_framework.permissions import IsAuthenticated
|
|
11
|
-
|
|
12
|
-
from ..services.django_q2_service import DjangoQ2Service
|
|
13
|
-
from ..serializers.django_q2 import (
|
|
14
|
-
DjangoQ2ScheduleSerializer,
|
|
15
|
-
DjangoQ2TaskSerializer,
|
|
16
|
-
DjangoQ2StatusSerializer,
|
|
17
|
-
DjangoQ2SummarySerializer,
|
|
18
|
-
)
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
class DjangoQ2ViewSet(viewsets.ViewSet):
|
|
22
|
-
"""
|
|
23
|
-
ViewSet for Django-Q2 task monitoring.
|
|
24
|
-
|
|
25
|
-
Provides endpoints for:
|
|
26
|
-
- Scheduled tasks list
|
|
27
|
-
- Recent task executions
|
|
28
|
-
- Cluster status
|
|
29
|
-
- Complete summary
|
|
30
|
-
"""
|
|
31
|
-
|
|
32
|
-
permission_classes = [IsAuthenticated]
|
|
33
|
-
|
|
34
|
-
@action(detail=False, methods=['get'])
|
|
35
|
-
def schedules(self, request):
|
|
36
|
-
"""
|
|
37
|
-
Get all scheduled tasks.
|
|
38
|
-
|
|
39
|
-
GET /api/django_q2/schedules/
|
|
40
|
-
"""
|
|
41
|
-
schedules = DjangoQ2Service.get_schedules()
|
|
42
|
-
serializer = DjangoQ2ScheduleSerializer(schedules, many=True)
|
|
43
|
-
return Response(serializer.data)
|
|
44
|
-
|
|
45
|
-
@action(detail=False, methods=['get'])
|
|
46
|
-
def tasks(self, request):
|
|
47
|
-
"""
|
|
48
|
-
Get recent task executions.
|
|
49
|
-
|
|
50
|
-
GET /api/django_q2/tasks/
|
|
51
|
-
Query params:
|
|
52
|
-
- limit: Number of tasks to return (default: 20)
|
|
53
|
-
"""
|
|
54
|
-
limit = int(request.query_params.get('limit', 20))
|
|
55
|
-
tasks = DjangoQ2Service.get_recent_tasks(limit=limit)
|
|
56
|
-
serializer = DjangoQ2TaskSerializer(tasks, many=True)
|
|
57
|
-
return Response(serializer.data)
|
|
58
|
-
|
|
59
|
-
@action(detail=False, methods=['get'])
|
|
60
|
-
def status(self, request):
|
|
61
|
-
"""
|
|
62
|
-
Get Django-Q2 cluster status.
|
|
63
|
-
|
|
64
|
-
GET /api/django_q2/status/
|
|
65
|
-
"""
|
|
66
|
-
status_data = DjangoQ2Service.get_cluster_status()
|
|
67
|
-
serializer = DjangoQ2StatusSerializer(status_data)
|
|
68
|
-
return Response(serializer.data)
|
|
69
|
-
|
|
70
|
-
def list(self, request):
|
|
71
|
-
"""
|
|
72
|
-
Get complete Django-Q2 summary.
|
|
73
|
-
|
|
74
|
-
GET /api/django_q2/
|
|
75
|
-
Returns status, schedules, and recent tasks.
|
|
76
|
-
"""
|
|
77
|
-
summary = DjangoQ2Service.get_summary()
|
|
78
|
-
serializer = DjangoQ2SummarySerializer(summary)
|
|
79
|
-
return Response(serializer.data)
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
ReArq tasks app for Django-CFG.
|
|
3
|
-
|
|
4
|
-
Provides async background task processing with Redis queue.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
__all__ = [
|
|
8
|
-
"ReArqClient",
|
|
9
|
-
"get_rearq_client",
|
|
10
|
-
"get_tasks_config",
|
|
11
|
-
"get_tasks_config_or_default",
|
|
12
|
-
"task",
|
|
13
|
-
"cron_task",
|
|
14
|
-
]
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def __getattr__(name):
|
|
18
|
-
"""Lazy imports to avoid loading ReArq at Django startup."""
|
|
19
|
-
if name in ("ReArqClient", "get_rearq_client"):
|
|
20
|
-
from .services.client import ReArqClient, get_rearq_client
|
|
21
|
-
return ReArqClient if name == "ReArqClient" else get_rearq_client
|
|
22
|
-
elif name in ("get_tasks_config", "get_tasks_config_or_default"):
|
|
23
|
-
from .services.config_helper import get_tasks_config, get_tasks_config_or_default
|
|
24
|
-
return get_tasks_config if name == "get_tasks_config" else get_tasks_config_or_default
|
|
25
|
-
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def task(queue: str = "default", **kwargs):
|
|
29
|
-
"""
|
|
30
|
-
Task decorator shortcut.
|
|
31
|
-
|
|
32
|
-
Example:
|
|
33
|
-
>>> from django_cfg.apps.tasks import task
|
|
34
|
-
>>>
|
|
35
|
-
>>> @task(queue="default")
|
|
36
|
-
>>> async def my_task(data: str):
|
|
37
|
-
... return f"Processed {data}"
|
|
38
|
-
>>>
|
|
39
|
-
>>> # Execute task
|
|
40
|
-
>>> job = await my_task.delay(data="test")
|
|
41
|
-
>>> result = await job.result(timeout=30)
|
|
42
|
-
"""
|
|
43
|
-
from .services.client import get_rearq_client
|
|
44
|
-
client = get_rearq_client()
|
|
45
|
-
return client.task(queue=queue, **kwargs)
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def cron_task(cron: str, **kwargs):
|
|
49
|
-
"""
|
|
50
|
-
Cron task decorator shortcut.
|
|
51
|
-
|
|
52
|
-
Args:
|
|
53
|
-
cron: Cron expression (e.g., "0 * * * *" for hourly)
|
|
54
|
-
|
|
55
|
-
Example:
|
|
56
|
-
>>> from django_cfg.apps.tasks import cron_task
|
|
57
|
-
>>>
|
|
58
|
-
>>> @cron_task(cron="0 0 * * *") # Daily at midnight
|
|
59
|
-
>>> async def daily_cleanup():
|
|
60
|
-
... return "Cleanup complete"
|
|
61
|
-
"""
|
|
62
|
-
from .services.client import get_rearq_client
|
|
63
|
-
client = get_rearq_client()
|
|
64
|
-
return client.cron_task(cron=cron, **kwargs)
|
|
@@ -1,265 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Task Log Admin.
|
|
3
|
-
|
|
4
|
-
PydanticAdmin for TaskLog model with custom computed fields.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
import json
|
|
8
|
-
|
|
9
|
-
from django.contrib import admin
|
|
10
|
-
from django_cfg.modules.django_admin import Icons, computed_field
|
|
11
|
-
from django_cfg.modules.django_admin.base import PydanticAdmin
|
|
12
|
-
|
|
13
|
-
from ..models import TaskLog
|
|
14
|
-
from .config import tasklog_config
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
@admin.register(TaskLog)
|
|
18
|
-
class TaskLogAdmin(PydanticAdmin):
|
|
19
|
-
"""
|
|
20
|
-
Task log admin with analytics and filtering.
|
|
21
|
-
|
|
22
|
-
Features:
|
|
23
|
-
- Color-coded status badges
|
|
24
|
-
- Duration display with performance indicators
|
|
25
|
-
- Retry count tracking
|
|
26
|
-
- Formatted JSON for arguments
|
|
27
|
-
- Error details with highlighted display
|
|
28
|
-
"""
|
|
29
|
-
|
|
30
|
-
config = tasklog_config
|
|
31
|
-
|
|
32
|
-
@computed_field("Queue", ordering="queue_name")
|
|
33
|
-
def queue_badge(self, obj):
|
|
34
|
-
"""Display queue name as badge."""
|
|
35
|
-
variant_map = {
|
|
36
|
-
"critical": "danger",
|
|
37
|
-
"high": "warning",
|
|
38
|
-
"default": "primary",
|
|
39
|
-
"low": "secondary",
|
|
40
|
-
"background": "secondary",
|
|
41
|
-
}
|
|
42
|
-
variant = variant_map.get(obj.queue_name, "info")
|
|
43
|
-
return self.html.badge(obj.queue_name, variant=variant, icon=Icons.LAYERS)
|
|
44
|
-
|
|
45
|
-
@computed_field("Status", ordering="status")
|
|
46
|
-
def status_badge(self, obj):
|
|
47
|
-
"""Display status with appropriate badge."""
|
|
48
|
-
variant_map = {
|
|
49
|
-
"queued": "secondary",
|
|
50
|
-
"in_progress": "info",
|
|
51
|
-
"completed": "success",
|
|
52
|
-
"failed": "danger",
|
|
53
|
-
"canceled": "warning",
|
|
54
|
-
}
|
|
55
|
-
icon_map = {
|
|
56
|
-
"queued": Icons.TIMER,
|
|
57
|
-
"in_progress": Icons.SPEED,
|
|
58
|
-
"completed": Icons.CHECK_CIRCLE,
|
|
59
|
-
"failed": Icons.ERROR,
|
|
60
|
-
"canceled": Icons.WARNING,
|
|
61
|
-
}
|
|
62
|
-
variant = variant_map.get(obj.status, "secondary")
|
|
63
|
-
icon = icon_map.get(obj.status, Icons.NOTIFICATIONS)
|
|
64
|
-
return self.html.badge(obj.get_status_display(), variant=variant, icon=icon)
|
|
65
|
-
|
|
66
|
-
@computed_field("Duration", ordering="duration_ms")
|
|
67
|
-
def duration_display(self, obj):
|
|
68
|
-
"""Display duration with color coding based on speed."""
|
|
69
|
-
if obj.duration_ms is None:
|
|
70
|
-
return self.html.empty()
|
|
71
|
-
|
|
72
|
-
# Color code based on duration
|
|
73
|
-
if obj.duration_ms < 1000: # < 1s
|
|
74
|
-
variant = "success" # Fast
|
|
75
|
-
icon = Icons.SPEED
|
|
76
|
-
elif obj.duration_ms < 5000: # < 5s
|
|
77
|
-
variant = "info" # Normal
|
|
78
|
-
icon = Icons.TIMER
|
|
79
|
-
elif obj.duration_ms < 30000: # < 30s
|
|
80
|
-
variant = "warning" # Slow
|
|
81
|
-
icon = Icons.TIMER
|
|
82
|
-
else:
|
|
83
|
-
variant = "danger" # Very slow
|
|
84
|
-
icon = Icons.ERROR
|
|
85
|
-
|
|
86
|
-
# Format duration
|
|
87
|
-
if obj.duration_ms < 1000:
|
|
88
|
-
duration_str = f"{obj.duration_ms}ms"
|
|
89
|
-
else:
|
|
90
|
-
duration_str = f"{obj.duration_ms / 1000:.2f}s"
|
|
91
|
-
|
|
92
|
-
return self.html.badge(duration_str, variant=variant, icon=icon)
|
|
93
|
-
|
|
94
|
-
def args_display(self, obj):
|
|
95
|
-
"""Display formatted JSON arguments."""
|
|
96
|
-
if not obj.args and not obj.kwargs:
|
|
97
|
-
return self.html.empty("No arguments")
|
|
98
|
-
|
|
99
|
-
try:
|
|
100
|
-
data = {}
|
|
101
|
-
if obj.args:
|
|
102
|
-
data["args"] = obj.args
|
|
103
|
-
if obj.kwargs:
|
|
104
|
-
data["kwargs"] = obj.kwargs
|
|
105
|
-
|
|
106
|
-
formatted = json.dumps(data, indent=2)
|
|
107
|
-
return f'<pre style="background: #f5f5f5; padding: 10px; border-radius: 4px; max-height: 400px; overflow: auto; font-size: 12px; line-height: 1.5;">{formatted}</pre>'
|
|
108
|
-
except Exception:
|
|
109
|
-
return str(data)
|
|
110
|
-
|
|
111
|
-
args_display.short_description = "Task Arguments"
|
|
112
|
-
|
|
113
|
-
def error_details_display(self, obj):
|
|
114
|
-
"""Display error information if task failed."""
|
|
115
|
-
if obj.is_successful or obj.status in ["queued", "in_progress"]:
|
|
116
|
-
return self.html.inline(
|
|
117
|
-
[
|
|
118
|
-
self.html.icon(Icons.CHECK_CIRCLE, size="sm"),
|
|
119
|
-
self.html.span("No errors", "text-green-600"),
|
|
120
|
-
]
|
|
121
|
-
)
|
|
122
|
-
|
|
123
|
-
if not obj.error_message:
|
|
124
|
-
return self.html.empty("No error message")
|
|
125
|
-
|
|
126
|
-
return self.html.inline(
|
|
127
|
-
[
|
|
128
|
-
self.html.icon(Icons.ERROR, size="sm"),
|
|
129
|
-
self.html.span(obj.error_message, "text-red-600 font-mono text-sm"),
|
|
130
|
-
]
|
|
131
|
-
)
|
|
132
|
-
|
|
133
|
-
error_details_display.short_description = "Error Details"
|
|
134
|
-
|
|
135
|
-
def retry_info_display(self, obj):
|
|
136
|
-
"""Display retry information."""
|
|
137
|
-
if obj.retry_count == 0:
|
|
138
|
-
return self.html.inline(
|
|
139
|
-
[
|
|
140
|
-
self.html.icon(Icons.CHECK_CIRCLE, size="sm"),
|
|
141
|
-
self.html.span("No retries", "text-gray-600"),
|
|
142
|
-
]
|
|
143
|
-
)
|
|
144
|
-
|
|
145
|
-
# Show retry count with warning if high
|
|
146
|
-
if obj.retry_count >= 3:
|
|
147
|
-
variant = "danger"
|
|
148
|
-
icon = Icons.ERROR
|
|
149
|
-
elif obj.retry_count >= 2:
|
|
150
|
-
variant = "warning"
|
|
151
|
-
icon = Icons.WARNING
|
|
152
|
-
else:
|
|
153
|
-
variant = "info"
|
|
154
|
-
icon = Icons.TIMER
|
|
155
|
-
|
|
156
|
-
return self.html.inline(
|
|
157
|
-
[
|
|
158
|
-
self.html.badge(f"{obj.retry_count} retries", variant=variant, icon=icon),
|
|
159
|
-
]
|
|
160
|
-
)
|
|
161
|
-
|
|
162
|
-
retry_info_display.short_description = "Retry Info"
|
|
163
|
-
|
|
164
|
-
def performance_summary(self, obj):
|
|
165
|
-
"""Display performance summary."""
|
|
166
|
-
stats = []
|
|
167
|
-
|
|
168
|
-
# Duration
|
|
169
|
-
if obj.duration_ms is not None:
|
|
170
|
-
if obj.duration_ms < 1000:
|
|
171
|
-
duration_str = f"{obj.duration_ms}ms"
|
|
172
|
-
else:
|
|
173
|
-
duration_str = f"{obj.duration_ms / 1000:.2f}s"
|
|
174
|
-
stats.append(
|
|
175
|
-
self.html.inline(
|
|
176
|
-
[
|
|
177
|
-
self.html.span("Duration:", "font-semibold"),
|
|
178
|
-
self.html.span(duration_str, "text-gray-600"),
|
|
179
|
-
],
|
|
180
|
-
separator=" ",
|
|
181
|
-
)
|
|
182
|
-
)
|
|
183
|
-
|
|
184
|
-
# Retry count
|
|
185
|
-
if obj.retry_count > 0:
|
|
186
|
-
stats.append(
|
|
187
|
-
self.html.inline(
|
|
188
|
-
[
|
|
189
|
-
self.html.span("Retries:", "font-semibold"),
|
|
190
|
-
self.html.badge(str(obj.retry_count), variant="warning"),
|
|
191
|
-
],
|
|
192
|
-
separator=" ",
|
|
193
|
-
)
|
|
194
|
-
)
|
|
195
|
-
|
|
196
|
-
# Worker ID
|
|
197
|
-
if obj.worker_id:
|
|
198
|
-
stats.append(
|
|
199
|
-
self.html.inline(
|
|
200
|
-
[
|
|
201
|
-
self.html.span("Worker:", "font-semibold"),
|
|
202
|
-
self.html.span(obj.worker_id, "text-gray-600 font-mono text-xs"),
|
|
203
|
-
],
|
|
204
|
-
separator=" ",
|
|
205
|
-
)
|
|
206
|
-
)
|
|
207
|
-
|
|
208
|
-
return "<br>".join(stats) if stats else self.html.empty()
|
|
209
|
-
|
|
210
|
-
performance_summary.short_description = "Performance"
|
|
211
|
-
|
|
212
|
-
# Fieldsets for detail view
|
|
213
|
-
def get_fieldsets(self, request, obj=None):
|
|
214
|
-
"""Dynamic fieldsets based on object state."""
|
|
215
|
-
fieldsets = [
|
|
216
|
-
(
|
|
217
|
-
"Task Information",
|
|
218
|
-
{
|
|
219
|
-
"fields": (
|
|
220
|
-
"id",
|
|
221
|
-
"job_id",
|
|
222
|
-
"task_name",
|
|
223
|
-
"queue_name",
|
|
224
|
-
"status",
|
|
225
|
-
"user",
|
|
226
|
-
)
|
|
227
|
-
},
|
|
228
|
-
),
|
|
229
|
-
(
|
|
230
|
-
"Arguments",
|
|
231
|
-
{"fields": ("args_display",), "classes": ("collapse",)},
|
|
232
|
-
),
|
|
233
|
-
(
|
|
234
|
-
"Performance",
|
|
235
|
-
{
|
|
236
|
-
"fields": (
|
|
237
|
-
"performance_summary",
|
|
238
|
-
"created_at",
|
|
239
|
-
"started_at",
|
|
240
|
-
"completed_at",
|
|
241
|
-
)
|
|
242
|
-
},
|
|
243
|
-
),
|
|
244
|
-
]
|
|
245
|
-
|
|
246
|
-
# Add error section only if failed
|
|
247
|
-
if obj and obj.is_failed:
|
|
248
|
-
fieldsets.insert(
|
|
249
|
-
2,
|
|
250
|
-
(
|
|
251
|
-
"Error Details",
|
|
252
|
-
{
|
|
253
|
-
"fields": (
|
|
254
|
-
"error_details_display",
|
|
255
|
-
"error_message",
|
|
256
|
-
"retry_info_display",
|
|
257
|
-
)
|
|
258
|
-
},
|
|
259
|
-
),
|
|
260
|
-
)
|
|
261
|
-
|
|
262
|
-
return fieldsets
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
__all__ = ["TaskLogAdmin"]
|
django_cfg/apps/tasks/apps.py
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
"""Django AppConfig for tasks app."""
|
|
2
|
-
from django.apps import AppConfig
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
class TasksConfig(AppConfig):
|
|
6
|
-
"""Django app configuration for ReArq tasks."""
|
|
7
|
-
|
|
8
|
-
default_auto_field = "django.db.models.BigAutoField"
|
|
9
|
-
name = "django_cfg.apps.tasks"
|
|
10
|
-
verbose_name = "Background Tasks"
|
|
11
|
-
|
|
12
|
-
def ready(self):
|
|
13
|
-
"""Initialize app when Django starts."""
|
|
14
|
-
# Import services to ensure client is available
|
|
15
|
-
from . import services # noqa: F401
|