django-cfg 1.5.1__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 (121) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/dashboard/TRANSACTION_FIX.md +73 -0
  3. django_cfg/apps/dashboard/serializers/__init__.py +0 -12
  4. django_cfg/apps/dashboard/serializers/activity.py +1 -1
  5. django_cfg/apps/dashboard/services/__init__.py +0 -2
  6. django_cfg/apps/dashboard/services/charts_service.py +4 -3
  7. django_cfg/apps/dashboard/services/statistics_service.py +11 -2
  8. django_cfg/apps/dashboard/services/system_health_service.py +64 -106
  9. django_cfg/apps/dashboard/urls.py +0 -2
  10. django_cfg/apps/dashboard/views/__init__.py +0 -2
  11. django_cfg/apps/dashboard/views/commands_views.py +3 -6
  12. django_cfg/apps/dashboard/views/overview_views.py +14 -13
  13. django_cfg/apps/knowbase/apps.py +2 -2
  14. django_cfg/apps/maintenance/admin/api_key_admin.py +2 -3
  15. django_cfg/apps/newsletter/admin/newsletter_admin.py +12 -11
  16. django_cfg/apps/rq/__init__.py +9 -0
  17. django_cfg/apps/rq/apps.py +80 -0
  18. django_cfg/apps/rq/management/__init__.py +1 -0
  19. django_cfg/apps/rq/management/commands/__init__.py +1 -0
  20. django_cfg/apps/rq/management/commands/rqscheduler.py +31 -0
  21. django_cfg/apps/rq/management/commands/rqstats.py +33 -0
  22. django_cfg/apps/rq/management/commands/rqworker.py +31 -0
  23. django_cfg/apps/rq/management/commands/rqworker_pool.py +27 -0
  24. django_cfg/apps/rq/serializers/__init__.py +40 -0
  25. django_cfg/apps/rq/serializers/health.py +60 -0
  26. django_cfg/apps/rq/serializers/job.py +100 -0
  27. django_cfg/apps/rq/serializers/queue.py +80 -0
  28. django_cfg/apps/rq/serializers/schedule.py +178 -0
  29. django_cfg/apps/rq/serializers/testing.py +139 -0
  30. django_cfg/apps/rq/serializers/worker.py +58 -0
  31. django_cfg/apps/rq/services/__init__.py +25 -0
  32. django_cfg/apps/rq/services/config_helper.py +233 -0
  33. django_cfg/apps/rq/services/models/README.md +417 -0
  34. django_cfg/apps/rq/services/models/__init__.py +30 -0
  35. django_cfg/apps/rq/services/models/event.py +123 -0
  36. django_cfg/apps/rq/services/models/job.py +99 -0
  37. django_cfg/apps/rq/services/models/queue.py +92 -0
  38. django_cfg/apps/rq/services/models/worker.py +104 -0
  39. django_cfg/apps/rq/services/rq_converters.py +183 -0
  40. django_cfg/apps/rq/tasks/__init__.py +23 -0
  41. django_cfg/apps/rq/tasks/demo_tasks.py +284 -0
  42. django_cfg/apps/rq/urls.py +54 -0
  43. django_cfg/apps/rq/views/__init__.py +19 -0
  44. django_cfg/apps/rq/views/jobs.py +882 -0
  45. django_cfg/apps/rq/views/monitoring.py +248 -0
  46. django_cfg/apps/rq/views/queues.py +261 -0
  47. django_cfg/apps/rq/views/schedule.py +400 -0
  48. django_cfg/apps/rq/views/testing.py +761 -0
  49. django_cfg/apps/rq/views/workers.py +195 -0
  50. django_cfg/apps/urls.py +6 -7
  51. django_cfg/core/base/config_model.py +10 -26
  52. django_cfg/core/builders/apps_builder.py +4 -11
  53. django_cfg/core/generation/integration_generators/__init__.py +3 -6
  54. django_cfg/core/generation/integration_generators/django_rq.py +80 -0
  55. django_cfg/core/generation/orchestrator.py +9 -19
  56. django_cfg/core/integration/display/startup.py +6 -20
  57. django_cfg/mixins/__init__.py +2 -0
  58. django_cfg/mixins/superadmin_api.py +59 -0
  59. django_cfg/models/__init__.py +3 -3
  60. django_cfg/models/django/__init__.py +3 -3
  61. django_cfg/models/django/django_rq.py +621 -0
  62. django_cfg/models/django/revolution_legacy.py +1 -1
  63. django_cfg/modules/base.py +4 -6
  64. django_cfg/modules/django_admin/config/background_task_config.py +4 -4
  65. django_cfg/modules/django_admin/utils/html/composition.py +9 -2
  66. django_cfg/modules/django_unfold/navigation.py +1 -26
  67. django_cfg/pyproject.toml +4 -4
  68. django_cfg/registry/core.py +4 -7
  69. django_cfg/static/frontend/admin.zip +0 -0
  70. django_cfg/templates/admin/constance/includes/results_list.html +73 -0
  71. django_cfg/templates/admin/index.html +187 -62
  72. django_cfg/templatetags/django_cfg.py +61 -1
  73. {django_cfg-1.5.1.dist-info → django_cfg-1.5.2.dist-info}/METADATA +5 -6
  74. {django_cfg-1.5.1.dist-info → django_cfg-1.5.2.dist-info}/RECORD +77 -82
  75. django_cfg/apps/dashboard/permissions.py +0 -48
  76. django_cfg/apps/dashboard/serializers/django_q2.py +0 -50
  77. django_cfg/apps/dashboard/services/django_q2_service.py +0 -159
  78. django_cfg/apps/dashboard/views/django_q2_views.py +0 -79
  79. django_cfg/apps/tasks/__init__.py +0 -64
  80. django_cfg/apps/tasks/admin/__init__.py +0 -4
  81. django_cfg/apps/tasks/admin/config.py +0 -98
  82. django_cfg/apps/tasks/admin/task_log.py +0 -238
  83. django_cfg/apps/tasks/apps.py +0 -15
  84. django_cfg/apps/tasks/filters/__init__.py +0 -10
  85. django_cfg/apps/tasks/filters/task_log.py +0 -121
  86. django_cfg/apps/tasks/migrations/0001_initial.py +0 -196
  87. django_cfg/apps/tasks/migrations/0002_delete_tasklog.py +0 -16
  88. django_cfg/apps/tasks/migrations/__init__.py +0 -0
  89. django_cfg/apps/tasks/models/__init__.py +0 -4
  90. django_cfg/apps/tasks/models/task_log.py +0 -246
  91. django_cfg/apps/tasks/serializers/__init__.py +0 -28
  92. django_cfg/apps/tasks/serializers/task_log.py +0 -249
  93. django_cfg/apps/tasks/services/__init__.py +0 -10
  94. django_cfg/apps/tasks/services/client/__init__.py +0 -7
  95. django_cfg/apps/tasks/services/client/client.py +0 -234
  96. django_cfg/apps/tasks/services/config_helper.py +0 -63
  97. django_cfg/apps/tasks/services/sync.py +0 -204
  98. django_cfg/apps/tasks/urls.py +0 -16
  99. django_cfg/apps/tasks/views/__init__.py +0 -10
  100. django_cfg/apps/tasks/views/task_log.py +0 -41
  101. django_cfg/apps/tasks/views/task_log_base.py +0 -41
  102. django_cfg/apps/tasks/views/task_log_overview.py +0 -100
  103. django_cfg/apps/tasks/views/task_log_related.py +0 -41
  104. django_cfg/apps/tasks/views/task_log_stats.py +0 -91
  105. django_cfg/apps/tasks/views/task_log_timeline.py +0 -81
  106. django_cfg/core/generation/integration_generators/django_q2.py +0 -133
  107. django_cfg/core/generation/integration_generators/tasks.py +0 -88
  108. django_cfg/models/django/django_q2.py +0 -514
  109. django_cfg/models/tasks/__init__.py +0 -49
  110. django_cfg/models/tasks/backends.py +0 -122
  111. django_cfg/models/tasks/config.py +0 -209
  112. django_cfg/models/tasks/utils.py +0 -162
  113. django_cfg/modules/django_q2/README.md +0 -140
  114. django_cfg/modules/django_q2/__init__.py +0 -8
  115. django_cfg/modules/django_q2/apps.py +0 -107
  116. django_cfg/modules/django_q2/management/__init__.py +0 -0
  117. django_cfg/modules/django_q2/management/commands/__init__.py +0 -0
  118. django_cfg/modules/django_q2/management/commands/sync_django_q_schedules.py +0 -74
  119. {django_cfg-1.5.1.dist-info → django_cfg-1.5.2.dist-info}/WHEEL +0 -0
  120. {django_cfg-1.5.1.dist-info → django_cfg-1.5.2.dist-info}/entry_points.txt +0 -0
  121. {django_cfg-1.5.1.dist-info → django_cfg-1.5.2.dist-info}/licenses/LICENSE +0 -0
@@ -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,98 +0,0 @@
1
- """
2
- Admin configuration for Task models.
3
-
4
- Declarative AdminConfig using PydanticAdmin patterns.
5
- """
6
-
7
- from django_cfg.modules.django_admin import (
8
- AdminConfig,
9
- BadgeField,
10
- DateTimeField,
11
- Icons,
12
- UserField,
13
- )
14
-
15
- from ..models import TaskLog
16
-
17
-
18
- # Declarative configuration for TaskLog
19
- tasklog_config = AdminConfig(
20
- model=TaskLog,
21
-
22
- # Performance optimization
23
- select_related=["user"],
24
-
25
- # List display
26
- list_display=[
27
- "task_name",
28
- "queue_badge",
29
- "status_badge",
30
- "user",
31
- "duration_display",
32
- "retry_count",
33
- "created_at",
34
- "completed_at",
35
- ],
36
-
37
- # Auto-generated display methods
38
- display_fields=[
39
- BadgeField(
40
- name="queue_name",
41
- title="Queue",
42
- variant="info",
43
- icon=Icons.LAYERS,
44
- ),
45
- BadgeField(
46
- name="status",
47
- title="Status",
48
- label_map={
49
- "queued": "secondary",
50
- "in_progress": "info",
51
- "completed": "success",
52
- "failed": "danger",
53
- "canceled": "warning",
54
- },
55
- ),
56
- UserField(name="user", title="User", header=True),
57
- DateTimeField(name="created_at", title="Created", ordering="created_at"),
58
- DateTimeField(name="started_at", title="Started", ordering="started_at"),
59
- DateTimeField(name="completed_at", title="Completed", ordering="completed_at"),
60
- ],
61
-
62
- # Filters
63
- list_filter=["status", "task_name", "queue_name", "created_at"],
64
- search_fields=[
65
- "job_id",
66
- "task_name",
67
- "worker_id",
68
- "error_message",
69
- "user__username",
70
- "user__email",
71
- ],
72
-
73
- # Autocomplete for user field
74
- autocomplete_fields=["user"],
75
-
76
- # Readonly fields
77
- readonly_fields=[
78
- "id",
79
- "job_id",
80
- "created_at",
81
- "started_at",
82
- "completed_at",
83
- "duration_ms",
84
- "worker_id",
85
- ],
86
-
87
- # Date hierarchy
88
- date_hierarchy="created_at",
89
-
90
- # Per page
91
- list_per_page=50,
92
-
93
- # Ordering
94
- ordering=["-created_at"],
95
- )
96
-
97
-
98
- __all__ = ["tasklog_config"]
@@ -1,238 +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 self.html.code_block(formatted, language="json", max_height="400px")
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
- self.html.icon(Icons.CHECK_CIRCLE, size="sm"),
118
- self.html.text("No errors", variant="success"),
119
- separator=" "
120
- )
121
-
122
- if not obj.error_message:
123
- return self.html.empty("No error message")
124
-
125
- return self.html.inline(
126
- self.html.icon(Icons.ERROR, size="sm"),
127
- self.html.code(obj.error_message, css_class="text-font-danger-light dark:text-font-danger-dark text-sm"),
128
- separator=" "
129
- )
130
-
131
- error_details_display.short_description = "Error Details"
132
-
133
- def retry_info_display(self, obj):
134
- """Display retry information."""
135
- if obj.retry_count == 0:
136
- return self.html.inline(
137
- self.html.icon(Icons.CHECK_CIRCLE, size="sm"),
138
- self.html.text("No retries", muted=True),
139
- separator=" "
140
- )
141
-
142
- # Show retry count with warning if high
143
- if obj.retry_count >= 3:
144
- variant = "danger"
145
- icon = Icons.ERROR
146
- elif obj.retry_count >= 2:
147
- variant = "warning"
148
- icon = Icons.WARNING
149
- else:
150
- variant = "info"
151
- icon = Icons.TIMER
152
-
153
- return self.html.badge(f"{obj.retry_count} retries", variant=variant, icon=icon)
154
-
155
- retry_info_display.short_description = "Retry Info"
156
-
157
- def performance_summary(self, obj):
158
- """Display performance summary."""
159
- # Duration
160
- duration_line = None
161
- if obj.duration_ms is not None:
162
- if obj.duration_ms < 1000:
163
- duration_str = f"{obj.duration_ms}ms"
164
- else:
165
- duration_str = f"{obj.duration_ms / 1000:.2f}s"
166
- duration_line = self.html.key_value("Duration", duration_str)
167
-
168
- # Retry count
169
- retry_line = None
170
- if obj.retry_count > 0:
171
- retry_line = self.html.key_value(
172
- "Retries",
173
- self.html.badge(str(obj.retry_count), variant="warning")
174
- )
175
-
176
- # Worker ID
177
- worker_line = None
178
- if obj.worker_id:
179
- worker_line = self.html.key_value("Worker", self.html.code(obj.worker_id, css_class="text-xs"))
180
-
181
- return self.html.breakdown(duration_line, retry_line, worker_line) if (duration_line or retry_line or worker_line) else self.html.empty()
182
-
183
- performance_summary.short_description = "Performance"
184
-
185
- # Fieldsets for detail view
186
- def get_fieldsets(self, request, obj=None):
187
- """Dynamic fieldsets based on object state."""
188
- fieldsets = [
189
- (
190
- "Task Information",
191
- {
192
- "fields": (
193
- "id",
194
- "job_id",
195
- "task_name",
196
- "queue_name",
197
- "status",
198
- "user",
199
- )
200
- },
201
- ),
202
- (
203
- "Arguments",
204
- {"fields": ("args_display",), "classes": ("collapse",)},
205
- ),
206
- (
207
- "Performance",
208
- {
209
- "fields": (
210
- "performance_summary",
211
- "created_at",
212
- "started_at",
213
- "completed_at",
214
- )
215
- },
216
- ),
217
- ]
218
-
219
- # Add error section only if failed
220
- if obj and obj.is_failed:
221
- fieldsets.insert(
222
- 2,
223
- (
224
- "Error Details",
225
- {
226
- "fields": (
227
- "error_details_display",
228
- "error_message",
229
- "retry_info_display",
230
- )
231
- },
232
- ),
233
- )
234
-
235
- return fieldsets
236
-
237
-
238
- __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
- ]
@@ -1,121 +0,0 @@
1
- """
2
- TaskLog FilterSet for advanced filtering.
3
-
4
- Provides comprehensive filtering capabilities for task logs.
5
- """
6
- import django_filters
7
- from django.db.models import Q
8
-
9
- from ..models import TaskLog
10
-
11
-
12
- class TaskLogFilter(django_filters.FilterSet):
13
- """
14
- Advanced FilterSet for TaskLog.
15
-
16
- Matches ReArq API query parameters where possible.
17
-
18
- Provides filters for:
19
- - Task name (exact, contains)
20
- - Queue name (exact, in)
21
- - Status (exact, in) - matching ReArq JobStatus
22
- - Date ranges (enqueue, start, finish)
23
- - Duration ranges
24
- - Retry count ranges
25
- - Success/failure flags
26
- """
27
-
28
- # Task name filters (matching ReArq "task" param)
29
- task = django_filters.CharFilter(field_name='task_name', lookup_expr='exact')
30
- task_name = django_filters.CharFilter(lookup_expr='icontains')
31
- task_name_exact = django_filters.CharFilter(field_name='task_name', lookup_expr='exact')
32
-
33
- # Queue filters
34
- queue_name = django_filters.CharFilter(lookup_expr='exact')
35
- queue_name_in = django_filters.BaseInFilter(field_name='queue_name')
36
-
37
- # Status filters (matching ReArq "status" param)
38
- status = django_filters.CharFilter(lookup_expr='exact')
39
- status_in = django_filters.BaseInFilter(field_name='status')
40
-
41
- # Success filter (matching ReArq JobResult)
42
- success = django_filters.BooleanFilter()
43
-
44
- # Boolean flags (computed properties)
45
- is_completed = django_filters.BooleanFilter(method='filter_is_completed')
46
- is_successful = django_filters.BooleanFilter(method='filter_is_successful')
47
- is_failed = django_filters.BooleanFilter(method='filter_is_failed')
48
-
49
- # Date range filters (matching ReArq "start_time" / "end_time")
50
- start_time = django_filters.DateTimeFilter(field_name='enqueue_time', lookup_expr='gte')
51
- end_time = django_filters.DateTimeFilter(field_name='enqueue_time', lookup_expr='lte')
52
-
53
- # Additional date filters
54
- enqueue_after = django_filters.DateTimeFilter(field_name='enqueue_time', lookup_expr='gte')
55
- enqueue_before = django_filters.DateTimeFilter(field_name='enqueue_time', lookup_expr='lte')
56
-
57
- start_after = django_filters.DateTimeFilter(field_name='start_time', lookup_expr='gte')
58
- start_before = django_filters.DateTimeFilter(field_name='start_time', lookup_expr='lte')
59
-
60
- finish_after = django_filters.DateTimeFilter(field_name='finish_time', lookup_expr='gte')
61
- finish_before = django_filters.DateTimeFilter(field_name='finish_time', lookup_expr='lte')
62
-
63
- # Django-specific filters
64
- created_after = django_filters.DateTimeFilter(field_name='created_at', lookup_expr='gte')
65
- created_before = django_filters.DateTimeFilter(field_name='created_at', lookup_expr='lte')
66
-
67
- # Duration filters (milliseconds)
68
- duration_min = django_filters.NumberFilter(field_name='duration_ms', lookup_expr='gte')
69
- duration_max = django_filters.NumberFilter(field_name='duration_ms', lookup_expr='lte')
70
-
71
- # Retry filters (matching ReArq Job fields)
72
- job_retries_min = django_filters.NumberFilter(field_name='job_retries', lookup_expr='gte')
73
- job_retries_max = django_filters.NumberFilter(field_name='job_retries', lookup_expr='lte')
74
-
75
- # Job ID filter (matching ReArq "job_id" param)
76
- job_id = django_filters.CharFilter(lookup_expr='exact')
77
-
78
- # Worker filter (matching ReArq JobResult "worker" param)
79
- worker = django_filters.CharFilter(field_name='worker_id', lookup_expr='exact')
80
-
81
- # Error message search
82
- has_error = django_filters.BooleanFilter(method='filter_has_error')
83
-
84
- class Meta:
85
- model = TaskLog
86
- fields = [
87
- 'task_name',
88
- 'queue_name',
89
- 'status',
90
- 'success',
91
- 'job_id',
92
- 'worker_id',
93
- ]
94
-
95
- def filter_has_error(self, queryset, name, value):
96
- """Filter tasks with/without errors."""
97
- if value:
98
- return queryset.filter(error_message__isnull=False).exclude(error_message='')
99
- else:
100
- return queryset.filter(Q(error_message__isnull=True) | Q(error_message=''))
101
-
102
- def filter_is_completed(self, queryset, name, value):
103
- """Filter by completed status."""
104
- if value:
105
- return queryset.filter(status__in=['success', 'failed', 'expired', 'canceled'])
106
- else:
107
- return queryset.filter(status__in=['deferred', 'queued', 'in_progress'])
108
-
109
- def filter_is_successful(self, queryset, name, value):
110
- """Filter by successful completion."""
111
- if value:
112
- return queryset.filter(status='success', success=True)
113
- else:
114
- return queryset.exclude(status='success', success=True)
115
-
116
- def filter_is_failed(self, queryset, name, value):
117
- """Filter by failed status."""
118
- if value:
119
- return queryset.filter(Q(status__in=['failed', 'expired']) | Q(success=False))
120
- else:
121
- return queryset.exclude(Q(status__in=['failed', 'expired']) | Q(success=False))