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
@@ -0,0 +1,98 @@
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"]
@@ -0,0 +1,265 @@
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,17 +1,15 @@
1
- """
2
- Django CFG Tasks App Configuration
3
- """
4
-
1
+ """Django AppConfig for tasks app."""
5
2
  from django.apps import AppConfig
6
3
 
7
4
 
8
5
  class TasksConfig(AppConfig):
9
- """Configuration for Django CFG Tasks app."""
6
+ """Django app configuration for ReArq tasks."""
10
7
 
11
- default_auto_field = 'django.db.models.BigAutoField'
12
- name = 'django_cfg.apps.tasks'
13
- verbose_name = 'Background Tasks'
8
+ default_auto_field = "django.db.models.BigAutoField"
9
+ name = "django_cfg.apps.tasks"
10
+ verbose_name = "Background Tasks"
14
11
 
15
12
  def ready(self):
16
13
  """Initialize app when Django starts."""
17
- pass
14
+ # Import services to ensure client is available
15
+ from . import services # noqa: F401
@@ -0,0 +1,10 @@
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
+ ]
@@ -0,0 +1,121 @@
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))