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.

Files changed (182) hide show
  1. django_cfg/__init__.py +8 -4
  2. django_cfg/apps/centrifugo/admin/centrifugo_log.py +33 -71
  3. django_cfg/apps/dashboard/TRANSACTION_FIX.md +73 -0
  4. django_cfg/apps/dashboard/serializers/__init__.py +0 -12
  5. django_cfg/apps/dashboard/serializers/activity.py +1 -1
  6. django_cfg/apps/dashboard/services/__init__.py +0 -2
  7. django_cfg/apps/dashboard/services/charts_service.py +4 -3
  8. django_cfg/apps/dashboard/services/statistics_service.py +11 -2
  9. django_cfg/apps/dashboard/services/system_health_service.py +64 -106
  10. django_cfg/apps/dashboard/urls.py +0 -2
  11. django_cfg/apps/dashboard/views/__init__.py +0 -2
  12. django_cfg/apps/dashboard/views/commands_views.py +3 -6
  13. django_cfg/apps/dashboard/views/overview_views.py +14 -13
  14. django_cfg/apps/grpc/__init__.py +9 -0
  15. django_cfg/apps/grpc/admin/__init__.py +11 -0
  16. django_cfg/apps/{tasks → grpc}/admin/config.py +32 -41
  17. django_cfg/apps/grpc/admin/grpc_request_log.py +252 -0
  18. django_cfg/apps/grpc/apps.py +28 -0
  19. django_cfg/apps/grpc/auth/__init__.py +9 -0
  20. django_cfg/apps/grpc/auth/jwt_auth.py +295 -0
  21. django_cfg/apps/grpc/interceptors/__init__.py +19 -0
  22. django_cfg/apps/grpc/interceptors/errors.py +241 -0
  23. django_cfg/apps/grpc/interceptors/logging.py +270 -0
  24. django_cfg/apps/grpc/interceptors/metrics.py +306 -0
  25. django_cfg/apps/grpc/interceptors/request_logger.py +515 -0
  26. django_cfg/apps/grpc/management/__init__.py +1 -0
  27. django_cfg/apps/grpc/management/commands/rungrpc.py +302 -0
  28. django_cfg/apps/grpc/managers/__init__.py +10 -0
  29. django_cfg/apps/grpc/managers/grpc_request_log.py +310 -0
  30. django_cfg/apps/grpc/migrations/0001_initial.py +69 -0
  31. django_cfg/apps/grpc/migrations/0002_rename_django_cfg__service_4c4a8e_idx_django_cfg__service_584308_idx_and_more.py +38 -0
  32. django_cfg/apps/grpc/models/__init__.py +9 -0
  33. django_cfg/apps/grpc/models/grpc_request_log.py +219 -0
  34. django_cfg/apps/grpc/serializers/__init__.py +23 -0
  35. django_cfg/apps/grpc/serializers/health.py +18 -0
  36. django_cfg/apps/grpc/serializers/requests.py +18 -0
  37. django_cfg/apps/grpc/serializers/services.py +50 -0
  38. django_cfg/apps/grpc/serializers/stats.py +22 -0
  39. django_cfg/apps/grpc/services/__init__.py +16 -0
  40. django_cfg/apps/grpc/services/base.py +375 -0
  41. django_cfg/apps/grpc/services/discovery.py +415 -0
  42. django_cfg/apps/grpc/urls.py +23 -0
  43. django_cfg/apps/grpc/utils/__init__.py +13 -0
  44. django_cfg/apps/grpc/utils/proto_gen.py +423 -0
  45. django_cfg/apps/grpc/views/__init__.py +9 -0
  46. django_cfg/apps/grpc/views/monitoring.py +497 -0
  47. django_cfg/apps/knowbase/apps.py +2 -2
  48. django_cfg/apps/maintenance/admin/api_key_admin.py +7 -9
  49. django_cfg/apps/maintenance/admin/site_admin.py +5 -4
  50. django_cfg/apps/newsletter/admin/newsletter_admin.py +12 -11
  51. django_cfg/apps/payments/admin/balance_admin.py +26 -36
  52. django_cfg/apps/payments/admin/payment_admin.py +65 -85
  53. django_cfg/apps/payments/admin/withdrawal_admin.py +65 -100
  54. django_cfg/apps/rq/__init__.py +9 -0
  55. django_cfg/apps/rq/apps.py +80 -0
  56. django_cfg/apps/rq/management/__init__.py +1 -0
  57. django_cfg/apps/rq/management/commands/__init__.py +1 -0
  58. django_cfg/apps/rq/management/commands/rqscheduler.py +31 -0
  59. django_cfg/apps/rq/management/commands/rqstats.py +33 -0
  60. django_cfg/apps/rq/management/commands/rqworker.py +31 -0
  61. django_cfg/apps/rq/management/commands/rqworker_pool.py +27 -0
  62. django_cfg/apps/rq/serializers/__init__.py +40 -0
  63. django_cfg/apps/rq/serializers/health.py +60 -0
  64. django_cfg/apps/rq/serializers/job.py +100 -0
  65. django_cfg/apps/rq/serializers/queue.py +80 -0
  66. django_cfg/apps/rq/serializers/schedule.py +178 -0
  67. django_cfg/apps/rq/serializers/testing.py +139 -0
  68. django_cfg/apps/rq/serializers/worker.py +58 -0
  69. django_cfg/apps/rq/services/__init__.py +25 -0
  70. django_cfg/apps/rq/services/config_helper.py +233 -0
  71. django_cfg/apps/rq/services/models/README.md +417 -0
  72. django_cfg/apps/rq/services/models/__init__.py +30 -0
  73. django_cfg/apps/rq/services/models/event.py +123 -0
  74. django_cfg/apps/rq/services/models/job.py +99 -0
  75. django_cfg/apps/rq/services/models/queue.py +92 -0
  76. django_cfg/apps/rq/services/models/worker.py +104 -0
  77. django_cfg/apps/rq/services/rq_converters.py +183 -0
  78. django_cfg/apps/rq/tasks/__init__.py +23 -0
  79. django_cfg/apps/rq/tasks/demo_tasks.py +284 -0
  80. django_cfg/apps/rq/urls.py +54 -0
  81. django_cfg/apps/rq/views/__init__.py +19 -0
  82. django_cfg/apps/rq/views/jobs.py +882 -0
  83. django_cfg/apps/rq/views/monitoring.py +248 -0
  84. django_cfg/apps/rq/views/queues.py +261 -0
  85. django_cfg/apps/rq/views/schedule.py +400 -0
  86. django_cfg/apps/rq/views/testing.py +761 -0
  87. django_cfg/apps/rq/views/workers.py +195 -0
  88. django_cfg/apps/urls.py +13 -8
  89. django_cfg/config.py +106 -0
  90. django_cfg/core/base/config_model.py +16 -26
  91. django_cfg/core/builders/apps_builder.py +7 -11
  92. django_cfg/core/generation/integration_generators/__init__.py +3 -6
  93. django_cfg/core/generation/integration_generators/django_rq.py +80 -0
  94. django_cfg/core/generation/integration_generators/grpc_generator.py +318 -0
  95. django_cfg/core/generation/orchestrator.py +15 -15
  96. django_cfg/core/integration/display/startup.py +6 -20
  97. django_cfg/mixins/__init__.py +2 -0
  98. django_cfg/mixins/superadmin_api.py +59 -0
  99. django_cfg/models/__init__.py +3 -3
  100. django_cfg/models/api/grpc/__init__.py +59 -0
  101. django_cfg/models/api/grpc/config.py +364 -0
  102. django_cfg/models/django/__init__.py +3 -3
  103. django_cfg/models/django/django_rq.py +621 -0
  104. django_cfg/models/django/revolution_legacy.py +1 -1
  105. django_cfg/modules/base.py +19 -6
  106. django_cfg/modules/django_admin/base/pydantic_admin.py +2 -2
  107. django_cfg/modules/django_admin/config/background_task_config.py +4 -4
  108. django_cfg/modules/django_admin/utils/__init__.py +41 -3
  109. django_cfg/modules/django_admin/utils/badges/__init__.py +13 -0
  110. django_cfg/modules/django_admin/utils/{badges.py → badges/status_badges.py} +3 -3
  111. django_cfg/modules/django_admin/utils/displays/__init__.py +13 -0
  112. django_cfg/modules/django_admin/utils/{displays.py → displays/data_displays.py} +2 -2
  113. django_cfg/modules/django_admin/utils/html/__init__.py +26 -0
  114. django_cfg/modules/django_admin/utils/html/badges.py +47 -0
  115. django_cfg/modules/django_admin/utils/html/base.py +167 -0
  116. django_cfg/modules/django_admin/utils/html/code.py +87 -0
  117. django_cfg/modules/django_admin/utils/html/composition.py +205 -0
  118. django_cfg/modules/django_admin/utils/html/formatting.py +231 -0
  119. django_cfg/modules/django_admin/utils/html/keyvalue.py +219 -0
  120. django_cfg/modules/django_admin/utils/html/markdown_integration.py +108 -0
  121. django_cfg/modules/django_admin/utils/html/progress.py +127 -0
  122. django_cfg/modules/django_admin/utils/html_builder.py +55 -408
  123. django_cfg/modules/django_admin/utils/markdown/__init__.py +21 -0
  124. django_cfg/modules/django_unfold/navigation.py +21 -18
  125. django_cfg/pyproject.toml +4 -6
  126. django_cfg/registry/core.py +4 -7
  127. django_cfg/registry/modules.py +6 -0
  128. django_cfg/static/frontend/admin.zip +0 -0
  129. django_cfg/templates/admin/constance/includes/results_list.html +73 -0
  130. django_cfg/templates/admin/index.html +187 -62
  131. django_cfg/templatetags/django_cfg.py +61 -1
  132. {django_cfg-1.4.120.dist-info → django_cfg-1.5.2.dist-info}/METADATA +12 -4
  133. {django_cfg-1.4.120.dist-info → django_cfg-1.5.2.dist-info}/RECORD +140 -96
  134. django_cfg/apps/dashboard/permissions.py +0 -48
  135. django_cfg/apps/dashboard/serializers/django_q2.py +0 -50
  136. django_cfg/apps/dashboard/services/django_q2_service.py +0 -159
  137. django_cfg/apps/dashboard/views/django_q2_views.py +0 -79
  138. django_cfg/apps/tasks/__init__.py +0 -64
  139. django_cfg/apps/tasks/admin/__init__.py +0 -4
  140. django_cfg/apps/tasks/admin/task_log.py +0 -265
  141. django_cfg/apps/tasks/apps.py +0 -15
  142. django_cfg/apps/tasks/filters/__init__.py +0 -10
  143. django_cfg/apps/tasks/filters/task_log.py +0 -121
  144. django_cfg/apps/tasks/migrations/0001_initial.py +0 -196
  145. django_cfg/apps/tasks/migrations/0002_delete_tasklog.py +0 -16
  146. django_cfg/apps/tasks/models/__init__.py +0 -4
  147. django_cfg/apps/tasks/models/task_log.py +0 -246
  148. django_cfg/apps/tasks/serializers/__init__.py +0 -28
  149. django_cfg/apps/tasks/serializers/task_log.py +0 -249
  150. django_cfg/apps/tasks/services/__init__.py +0 -10
  151. django_cfg/apps/tasks/services/client/__init__.py +0 -7
  152. django_cfg/apps/tasks/services/client/client.py +0 -234
  153. django_cfg/apps/tasks/services/config_helper.py +0 -63
  154. django_cfg/apps/tasks/services/sync.py +0 -204
  155. django_cfg/apps/tasks/urls.py +0 -16
  156. django_cfg/apps/tasks/views/__init__.py +0 -10
  157. django_cfg/apps/tasks/views/task_log.py +0 -41
  158. django_cfg/apps/tasks/views/task_log_base.py +0 -41
  159. django_cfg/apps/tasks/views/task_log_overview.py +0 -100
  160. django_cfg/apps/tasks/views/task_log_related.py +0 -41
  161. django_cfg/apps/tasks/views/task_log_stats.py +0 -91
  162. django_cfg/apps/tasks/views/task_log_timeline.py +0 -81
  163. django_cfg/core/generation/integration_generators/django_q2.py +0 -133
  164. django_cfg/core/generation/integration_generators/tasks.py +0 -88
  165. django_cfg/models/django/django_q2.py +0 -514
  166. django_cfg/models/tasks/__init__.py +0 -49
  167. django_cfg/models/tasks/backends.py +0 -122
  168. django_cfg/models/tasks/config.py +0 -209
  169. django_cfg/models/tasks/utils.py +0 -162
  170. django_cfg/modules/django_admin/utils/CODE_BLOCK_DOCS.md +0 -396
  171. django_cfg/modules/django_q2/README.md +0 -140
  172. django_cfg/modules/django_q2/__init__.py +0 -8
  173. django_cfg/modules/django_q2/apps.py +0 -107
  174. django_cfg/modules/django_q2/management/commands/__init__.py +0 -0
  175. django_cfg/modules/django_q2/management/commands/sync_django_q_schedules.py +0 -74
  176. /django_cfg/apps/{tasks/migrations → grpc/management/commands}/__init__.py +0 -0
  177. /django_cfg/{modules/django_q2/management → apps/grpc/migrations}/__init__.py +0 -0
  178. /django_cfg/modules/django_admin/utils/{mermaid_plugin.py → markdown/mermaid_plugin.py} +0 -0
  179. /django_cfg/modules/django_admin/utils/{markdown_renderer.py → markdown/renderer.py} +0 -0
  180. {django_cfg-1.4.120.dist-info → django_cfg-1.5.2.dist-info}/WHEEL +0 -0
  181. {django_cfg-1.4.120.dist-info → django_cfg-1.5.2.dist-info}/entry_points.txt +0 -0
  182. {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,4 +0,0 @@
1
- """Admin configuration for tasks app."""
2
- from .config import tasklog_config
3
-
4
- __all__ = ["tasklog_config"]
@@ -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"]
@@ -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
@@ -1,10 +0,0 @@
1
- """
2
- ReArq Tasks Filters.
3
-
4
- Django-filter FilterSets for task log filtering.
5
- """
6
- from .task_log import TaskLogFilter
7
-
8
- __all__ = [
9
- "TaskLogFilter",
10
- ]