django-cfg 1.4.113__py3-none-any.whl → 1.4.114__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 +1 -1
- django_cfg/apps/dashboard/serializers/__init__.py +10 -8
- django_cfg/apps/dashboard/serializers/django_q2.py +50 -0
- django_cfg/apps/dashboard/services/__init__.py +2 -2
- django_cfg/apps/dashboard/services/django_q2_service.py +159 -0
- django_cfg/apps/dashboard/services/system_health_service.py +39 -26
- django_cfg/apps/dashboard/urls.py +2 -2
- django_cfg/apps/dashboard/views/__init__.py +2 -2
- django_cfg/apps/dashboard/views/django_q2_views.py +79 -0
- django_cfg/apps/tasks/migrations/0002_delete_tasklog.py +16 -0
- django_cfg/core/base/config_model.py +15 -5
- django_cfg/core/builders/apps_builder.py +3 -3
- django_cfg/core/generation/data_generators/cache.py +28 -2
- django_cfg/core/generation/integration_generators/__init__.py +4 -3
- django_cfg/core/generation/integration_generators/django_q2.py +133 -0
- django_cfg/core/generation/orchestrator.py +7 -7
- django_cfg/models/__init__.py +3 -3
- django_cfg/models/django/__init__.py +3 -3
- django_cfg/models/django/django_q2.py +491 -0
- django_cfg/pyproject.toml +2 -2
- django_cfg/registry/core.py +3 -3
- django_cfg/static/frontend/admin.zip +0 -0
- {django_cfg-1.4.113.dist-info → django_cfg-1.4.114.dist-info}/METADATA +3 -2
- {django_cfg-1.4.113.dist-info → django_cfg-1.4.114.dist-info}/RECORD +27 -26
- django_cfg/apps/dashboard/serializers/crontab.py +0 -84
- django_cfg/apps/dashboard/services/crontab_service.py +0 -210
- django_cfg/apps/dashboard/views/crontab_views.py +0 -72
- django_cfg/core/generation/integration_generators/crontab.py +0 -64
- django_cfg/models/django/crontab.py +0 -303
- {django_cfg-1.4.113.dist-info → django_cfg-1.4.114.dist-info}/WHEEL +0 -0
- {django_cfg-1.4.113.dist-info → django_cfg-1.4.114.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.4.113.dist-info → django_cfg-1.4.114.dist-info}/licenses/LICENSE +0 -0
django_cfg/__init__.py
CHANGED
|
@@ -22,10 +22,11 @@ from .commands import (
|
|
|
22
22
|
CommandHelpResponseSerializer,
|
|
23
23
|
)
|
|
24
24
|
from .apizones import APIZoneSerializer, APIZonesSummarySerializer
|
|
25
|
-
from .
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
from .django_q2 import (
|
|
26
|
+
DjangoQ2ScheduleSerializer,
|
|
27
|
+
DjangoQ2TaskSerializer,
|
|
28
|
+
DjangoQ2StatusSerializer,
|
|
29
|
+
DjangoQ2SummarySerializer,
|
|
29
30
|
)
|
|
30
31
|
|
|
31
32
|
__all__ = [
|
|
@@ -65,8 +66,9 @@ __all__ = [
|
|
|
65
66
|
'APIZoneSerializer',
|
|
66
67
|
'APIZonesSummarySerializer',
|
|
67
68
|
|
|
68
|
-
#
|
|
69
|
-
'
|
|
70
|
-
'
|
|
71
|
-
'
|
|
69
|
+
# Django-Q2
|
|
70
|
+
'DjangoQ2ScheduleSerializer',
|
|
71
|
+
'DjangoQ2TaskSerializer',
|
|
72
|
+
'DjangoQ2StatusSerializer',
|
|
73
|
+
'DjangoQ2SummarySerializer',
|
|
72
74
|
]
|
|
@@ -0,0 +1,50 @@
|
|
|
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)
|
|
@@ -11,7 +11,7 @@ from .charts_service import ChartsService
|
|
|
11
11
|
from .commands_service import CommandsService
|
|
12
12
|
from .apizones_service import APIZonesService
|
|
13
13
|
from .overview_service import OverviewService
|
|
14
|
-
from .
|
|
14
|
+
from .django_q2_service import DjangoQ2Service
|
|
15
15
|
|
|
16
16
|
__all__ = [
|
|
17
17
|
'StatisticsService',
|
|
@@ -20,5 +20,5 @@ __all__ = [
|
|
|
20
20
|
'CommandsService',
|
|
21
21
|
'APIZonesService',
|
|
22
22
|
'OverviewService',
|
|
23
|
-
'
|
|
23
|
+
'DjangoQ2Service',
|
|
24
24
|
]
|
|
@@ -0,0 +1,159 @@
|
|
|
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
|
+
}
|
|
@@ -191,73 +191,86 @@ class SystemHealthService:
|
|
|
191
191
|
'health_percentage': 0,
|
|
192
192
|
}
|
|
193
193
|
|
|
194
|
-
def
|
|
194
|
+
def check_django_q2_health(self) -> Dict[str, Any]:
|
|
195
195
|
"""
|
|
196
|
-
Check
|
|
196
|
+
Check Django-Q2 task scheduling configuration and status.
|
|
197
197
|
|
|
198
198
|
Returns:
|
|
199
|
-
Health status dictionary with
|
|
199
|
+
Health status dictionary with schedule count and cluster status
|
|
200
200
|
"""
|
|
201
201
|
try:
|
|
202
202
|
from django_cfg.core.config import get_current_config
|
|
203
203
|
|
|
204
204
|
config = get_current_config()
|
|
205
205
|
|
|
206
|
-
# Check if
|
|
207
|
-
if not hasattr(config, '
|
|
206
|
+
# Check if django_q2 is configured
|
|
207
|
+
if not hasattr(config, 'django_q2') or not config.django_q2:
|
|
208
208
|
return {
|
|
209
|
-
'component': '
|
|
209
|
+
'component': 'django_q2',
|
|
210
210
|
'status': 'info',
|
|
211
|
-
'description': '
|
|
211
|
+
'description': 'Django-Q2 scheduling not configured',
|
|
212
212
|
'last_check': datetime.now().isoformat(),
|
|
213
213
|
'health_percentage': 100,
|
|
214
214
|
'details': {
|
|
215
215
|
'enabled': False,
|
|
216
|
-
'
|
|
216
|
+
'schedules_count': 0,
|
|
217
217
|
}
|
|
218
218
|
}
|
|
219
219
|
|
|
220
|
-
|
|
220
|
+
django_q2_config = config.django_q2
|
|
221
221
|
|
|
222
222
|
# Check if enabled
|
|
223
|
-
if not
|
|
223
|
+
if not django_q2_config.enabled:
|
|
224
224
|
return {
|
|
225
|
-
'component': '
|
|
225
|
+
'component': 'django_q2',
|
|
226
226
|
'status': 'warning',
|
|
227
|
-
'description': '
|
|
227
|
+
'description': 'Django-Q2 scheduling is disabled',
|
|
228
228
|
'last_check': datetime.now().isoformat(),
|
|
229
229
|
'health_percentage': 50,
|
|
230
230
|
'details': {
|
|
231
231
|
'enabled': False,
|
|
232
|
-
'
|
|
232
|
+
'schedules_count': len(django_q2_config.schedules) if django_q2_config.schedules else 0,
|
|
233
233
|
}
|
|
234
234
|
}
|
|
235
235
|
|
|
236
|
-
# Count
|
|
237
|
-
|
|
238
|
-
|
|
236
|
+
# Count schedules
|
|
237
|
+
schedules_count = len(django_q2_config.schedules) if django_q2_config.schedules else 0
|
|
238
|
+
|
|
239
|
+
# Try to check cluster status from database
|
|
240
|
+
cluster_running = False
|
|
241
|
+
try:
|
|
242
|
+
from django_q.models import Schedule, Task
|
|
243
|
+
from django.utils import timezone
|
|
244
|
+
from datetime import timedelta
|
|
245
|
+
|
|
246
|
+
# Check for recent task activity
|
|
247
|
+
recent_task = Task.objects.filter(
|
|
248
|
+
started__gte=timezone.now() - timedelta(minutes=5)
|
|
249
|
+
).exists()
|
|
250
|
+
cluster_running = recent_task
|
|
251
|
+
except Exception:
|
|
252
|
+
pass
|
|
239
253
|
|
|
240
254
|
return {
|
|
241
|
-
'component': '
|
|
255
|
+
'component': 'django_q2',
|
|
242
256
|
'status': 'healthy',
|
|
243
|
-
'description': f'{
|
|
257
|
+
'description': f'{schedules_count} schedule(s) configured, cluster {"running" if cluster_running else "idle"}',
|
|
244
258
|
'last_check': datetime.now().isoformat(),
|
|
245
259
|
'health_percentage': 100,
|
|
246
260
|
'details': {
|
|
247
261
|
'enabled': True,
|
|
248
|
-
'
|
|
249
|
-
'
|
|
250
|
-
'
|
|
251
|
-
'comment': crontab_config.comment,
|
|
262
|
+
'schedules_count': schedules_count,
|
|
263
|
+
'cluster_running': cluster_running,
|
|
264
|
+
'workers': django_q2_config.workers,
|
|
252
265
|
}
|
|
253
266
|
}
|
|
254
267
|
|
|
255
268
|
except Exception as e:
|
|
256
|
-
self.logger.error(f"
|
|
269
|
+
self.logger.error(f"Django-Q2 health check failed: {e}")
|
|
257
270
|
return {
|
|
258
|
-
'component': '
|
|
271
|
+
'component': 'django_q2',
|
|
259
272
|
'status': 'error',
|
|
260
|
-
'description': f'
|
|
273
|
+
'description': f'Django-Q2 check error: {str(e)}',
|
|
261
274
|
'last_check': datetime.now().isoformat(),
|
|
262
275
|
'health_percentage': 0,
|
|
263
276
|
}
|
|
@@ -276,7 +289,7 @@ class SystemHealthService:
|
|
|
276
289
|
self.check_cache_health(),
|
|
277
290
|
self.check_queue_health(),
|
|
278
291
|
self.check_storage_health(),
|
|
279
|
-
self.
|
|
292
|
+
self.check_django_q2_health(),
|
|
280
293
|
]
|
|
281
294
|
|
|
282
295
|
return checks
|
|
@@ -21,7 +21,7 @@ from .views import (
|
|
|
21
21
|
ChartsViewSet,
|
|
22
22
|
CommandsViewSet,
|
|
23
23
|
APIZonesViewSet,
|
|
24
|
-
|
|
24
|
+
DjangoQ2ViewSet,
|
|
25
25
|
)
|
|
26
26
|
|
|
27
27
|
app_name = 'django_cfg_dashboard'
|
|
@@ -35,7 +35,7 @@ router.register(r'activity', ActivityViewSet, basename='activity')
|
|
|
35
35
|
router.register(r'charts', ChartsViewSet, basename='charts')
|
|
36
36
|
router.register(r'commands', CommandsViewSet, basename='commands')
|
|
37
37
|
router.register(r'zones', APIZonesViewSet, basename='zones')
|
|
38
|
-
router.register(r'
|
|
38
|
+
router.register(r'django_q2', DjangoQ2ViewSet, basename='django_q2')
|
|
39
39
|
|
|
40
40
|
urlpatterns = [
|
|
41
41
|
# RESTful API endpoints using ViewSets
|
|
@@ -11,7 +11,7 @@ from .activity_views import ActivityViewSet
|
|
|
11
11
|
from .charts_views import ChartsViewSet
|
|
12
12
|
from .commands_views import CommandsViewSet
|
|
13
13
|
from .apizones_views import APIZonesViewSet
|
|
14
|
-
from .
|
|
14
|
+
from .django_q2_views import DjangoQ2ViewSet
|
|
15
15
|
|
|
16
16
|
__all__ = [
|
|
17
17
|
'OverviewViewSet',
|
|
@@ -21,5 +21,5 @@ __all__ = [
|
|
|
21
21
|
'ChartsViewSet',
|
|
22
22
|
'CommandsViewSet',
|
|
23
23
|
'APIZonesViewSet',
|
|
24
|
-
'
|
|
24
|
+
'DjangoQ2ViewSet',
|
|
25
25
|
]
|
|
@@ -0,0 +1,79 @@
|
|
|
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)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Generated by Django 5.2.7 on 2025-10-31 05:54
|
|
2
|
+
|
|
3
|
+
from django.db import migrations
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
|
|
8
|
+
dependencies = [
|
|
9
|
+
("tasks", "0001_initial"),
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
operations = [
|
|
13
|
+
migrations.DeleteModel(
|
|
14
|
+
name="TaskLog",
|
|
15
|
+
),
|
|
16
|
+
]
|
|
@@ -20,7 +20,7 @@ from ...models import (
|
|
|
20
20
|
ApiKeys,
|
|
21
21
|
AxesConfig,
|
|
22
22
|
CacheConfig,
|
|
23
|
-
|
|
23
|
+
DjangoQ2Config,
|
|
24
24
|
CryptoFieldsConfig,
|
|
25
25
|
DatabaseConfig,
|
|
26
26
|
DRFConfig,
|
|
@@ -217,9 +217,19 @@ class DjangoConfig(BaseModel):
|
|
|
217
217
|
)
|
|
218
218
|
|
|
219
219
|
# === Cache Configuration ===
|
|
220
|
+
# Redis URL - used for automatic cache configuration if cache_default is not set
|
|
221
|
+
redis_url: Optional[str] = Field(
|
|
222
|
+
default=None,
|
|
223
|
+
description=(
|
|
224
|
+
"Redis connection URL (redis://host:port/db). "
|
|
225
|
+
"If set and cache_default is None, automatically creates RedisCache backend. "
|
|
226
|
+
"Also used by tasks (ReArq, Django-Q2) if they don't specify broker_url."
|
|
227
|
+
),
|
|
228
|
+
)
|
|
229
|
+
|
|
220
230
|
cache_default: Optional[CacheConfig] = Field(
|
|
221
231
|
default=None,
|
|
222
|
-
description="Default cache backend",
|
|
232
|
+
description="Default cache backend (auto-created from redis_url if not set)",
|
|
223
233
|
)
|
|
224
234
|
|
|
225
235
|
cache_sessions: Optional[CacheConfig] = Field(
|
|
@@ -319,10 +329,10 @@ class DjangoConfig(BaseModel):
|
|
|
319
329
|
description="Background task processing configuration (ReArq)",
|
|
320
330
|
)
|
|
321
331
|
|
|
322
|
-
# ===
|
|
323
|
-
|
|
332
|
+
# === Django-Q2 Task Scheduling ===
|
|
333
|
+
django_q2: Optional[DjangoQ2Config] = Field(
|
|
324
334
|
default=None,
|
|
325
|
-
description="
|
|
335
|
+
description="Django-Q2 task scheduling and queue configuration",
|
|
326
336
|
)
|
|
327
337
|
|
|
328
338
|
# === Centrifugo Configuration ===
|
|
@@ -163,9 +163,9 @@ class InstalledAppsBuilder:
|
|
|
163
163
|
# No external app needed - ReArq is embedded
|
|
164
164
|
apps.append("django_cfg.apps.tasks")
|
|
165
165
|
|
|
166
|
-
# Add django-
|
|
167
|
-
if hasattr(self.config, "
|
|
168
|
-
apps.append("
|
|
166
|
+
# Add django-q2 if enabled
|
|
167
|
+
if hasattr(self.config, "django_q2") and self.config.django_q2 and self.config.django_q2.enabled:
|
|
168
|
+
apps.append("django_q")
|
|
169
169
|
|
|
170
170
|
# Add DRF Tailwind theme module (uses Tailwind via CDN)
|
|
171
171
|
if self.config.enable_drf_tailwind:
|
|
@@ -58,13 +58,18 @@ class CacheSettingsGenerator:
|
|
|
58
58
|
|
|
59
59
|
# Default cache - always provide one
|
|
60
60
|
if self.config.cache_default:
|
|
61
|
+
# User explicitly configured cache_default
|
|
61
62
|
caches["default"] = self.config.cache_default.to_django_config(
|
|
62
63
|
self.config.env_mode,
|
|
63
64
|
self.config.debug,
|
|
64
65
|
"default"
|
|
65
66
|
)
|
|
67
|
+
elif self.config.redis_url:
|
|
68
|
+
# Auto-create Redis cache from redis_url
|
|
69
|
+
logger.info(f"Auto-creating Redis cache from redis_url: {self.config.redis_url}")
|
|
70
|
+
caches["default"] = self._get_redis_cache_config()
|
|
66
71
|
else:
|
|
67
|
-
#
|
|
72
|
+
# Fallback to default cache backend (LocMem/FileBased depending on env)
|
|
68
73
|
caches["default"] = self._get_default_cache_config()
|
|
69
74
|
|
|
70
75
|
# Sessions cache
|
|
@@ -88,9 +93,30 @@ class CacheSettingsGenerator:
|
|
|
88
93
|
|
|
89
94
|
return settings
|
|
90
95
|
|
|
96
|
+
def _get_redis_cache_config(self) -> Dict[str, Any]:
|
|
97
|
+
"""
|
|
98
|
+
Auto-create Redis cache from config.redis_url.
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
Dictionary with Redis cache backend configuration
|
|
102
|
+
"""
|
|
103
|
+
from ....models.infrastructure.cache import CacheConfig
|
|
104
|
+
|
|
105
|
+
redis_cache = CacheConfig(
|
|
106
|
+
redis_url=self.config.redis_url,
|
|
107
|
+
timeout=300, # 5 minutes default
|
|
108
|
+
max_connections=50,
|
|
109
|
+
key_prefix=self.config.project_name.lower().replace(" ", "_") if self.config.project_name else "django",
|
|
110
|
+
)
|
|
111
|
+
return redis_cache.to_django_config(
|
|
112
|
+
self.config.env_mode,
|
|
113
|
+
self.config.debug,
|
|
114
|
+
"default"
|
|
115
|
+
)
|
|
116
|
+
|
|
91
117
|
def _get_default_cache_config(self) -> Dict[str, Any]:
|
|
92
118
|
"""
|
|
93
|
-
Get default cache configuration.
|
|
119
|
+
Get default cache configuration (fallback when no redis_url).
|
|
94
120
|
|
|
95
121
|
Returns:
|
|
96
122
|
Dictionary with default cache backend configuration
|
|
@@ -6,11 +6,12 @@ Contains generators for third-party integrations and frameworks:
|
|
|
6
6
|
- External services (Telegram, Unfold, Constance)
|
|
7
7
|
- API frameworks (JWT, DRF, Spectacular, OpenAPI Client)
|
|
8
8
|
- Background tasks (ReArq)
|
|
9
|
-
-
|
|
9
|
+
- Task scheduling (django-q2)
|
|
10
|
+
- Tailwind CSS configuration
|
|
10
11
|
"""
|
|
11
12
|
|
|
12
13
|
from .api import APIFrameworksGenerator
|
|
13
|
-
from .
|
|
14
|
+
from .django_q2 import DjangoQ2SettingsGenerator
|
|
14
15
|
from .sessions import SessionSettingsGenerator
|
|
15
16
|
from .tasks import TasksSettingsGenerator
|
|
16
17
|
from .third_party import ThirdPartyIntegrationsGenerator
|
|
@@ -20,5 +21,5 @@ __all__ = [
|
|
|
20
21
|
"ThirdPartyIntegrationsGenerator",
|
|
21
22
|
"APIFrameworksGenerator",
|
|
22
23
|
"TasksSettingsGenerator",
|
|
23
|
-
"
|
|
24
|
+
"DjangoQ2SettingsGenerator",
|
|
24
25
|
]
|