django-cfg 1.5.1__py3-none-any.whl → 1.5.3__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.3.dist-info}/METADATA +5 -6
  74. {django_cfg-1.5.1.dist-info → django_cfg-1.5.3.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.3.dist-info}/WHEEL +0 -0
  120. {django_cfg-1.5.1.dist-info → django_cfg-1.5.3.dist-info}/entry_points.txt +0 -0
  121. {django_cfg-1.5.1.dist-info → django_cfg-1.5.3.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,139 @@
1
+ """
2
+ DRF serializers for RQ testing and simulation endpoints.
3
+ """
4
+
5
+ from rest_framework import serializers
6
+
7
+
8
+ class TestScenarioSerializer(serializers.Serializer):
9
+ """Serializer for available test scenarios."""
10
+
11
+ id = serializers.CharField(help_text="Scenario ID")
12
+ name = serializers.CharField(help_text="Scenario name")
13
+ description = serializers.CharField(help_text="Scenario description")
14
+ task_func = serializers.CharField(help_text="Task function path")
15
+ default_args = serializers.ListField(
16
+ child=serializers.JSONField(),
17
+ required=False,
18
+ default=list,
19
+ help_text="Default arguments"
20
+ )
21
+ default_kwargs = serializers.DictField(
22
+ required=False,
23
+ default=dict,
24
+ help_text="Default keyword arguments"
25
+ )
26
+ estimated_duration = serializers.IntegerField(
27
+ required=False,
28
+ allow_null=True,
29
+ help_text="Estimated duration in seconds"
30
+ )
31
+
32
+
33
+ class RunDemoRequestSerializer(serializers.Serializer):
34
+ """Serializer for running demo tasks."""
35
+
36
+ scenario = serializers.ChoiceField(
37
+ choices=[
38
+ 'success',
39
+ 'failure',
40
+ 'slow',
41
+ 'progress',
42
+ 'retry',
43
+ 'random',
44
+ 'memory',
45
+ 'cpu',
46
+ ],
47
+ help_text="Demo scenario to run"
48
+ )
49
+ queue = serializers.CharField(
50
+ default='default',
51
+ help_text="Queue name"
52
+ )
53
+ args = serializers.ListField(
54
+ child=serializers.JSONField(),
55
+ required=False,
56
+ default=list,
57
+ help_text="Task arguments"
58
+ )
59
+ kwargs = serializers.DictField(
60
+ required=False,
61
+ default=dict,
62
+ help_text="Task keyword arguments"
63
+ )
64
+ timeout = serializers.IntegerField(
65
+ required=False,
66
+ allow_null=True,
67
+ help_text="Job timeout in seconds"
68
+ )
69
+
70
+
71
+ class StressTestRequestSerializer(serializers.Serializer):
72
+ """Serializer for stress testing."""
73
+
74
+ num_jobs = serializers.IntegerField(
75
+ min_value=1,
76
+ max_value=1000,
77
+ default=10,
78
+ help_text="Number of jobs to create"
79
+ )
80
+ queue = serializers.CharField(
81
+ default='default',
82
+ help_text="Queue name"
83
+ )
84
+ scenario = serializers.ChoiceField(
85
+ choices=['success', 'failure', 'slow', 'random'],
86
+ default='success',
87
+ help_text="Task scenario"
88
+ )
89
+ duration = serializers.IntegerField(
90
+ min_value=1,
91
+ max_value=60,
92
+ default=2,
93
+ help_text="Task duration in seconds"
94
+ )
95
+
96
+
97
+ class TestingActionResponseSerializer(serializers.Serializer):
98
+ """Serializer for testing action responses."""
99
+
100
+ success = serializers.BooleanField(help_text="Action success status")
101
+ message = serializers.CharField(help_text="Action message")
102
+ job_ids = serializers.ListField(
103
+ child=serializers.CharField(),
104
+ required=False,
105
+ default=list,
106
+ help_text="Created job IDs"
107
+ )
108
+ count = serializers.IntegerField(
109
+ required=False,
110
+ allow_null=True,
111
+ help_text="Number of items affected"
112
+ )
113
+ metadata = serializers.DictField(
114
+ required=False,
115
+ default=dict,
116
+ help_text="Additional metadata"
117
+ )
118
+
119
+
120
+ class CleanupRequestSerializer(serializers.Serializer):
121
+ """Serializer for cleanup operations."""
122
+
123
+ queue = serializers.CharField(
124
+ required=False,
125
+ allow_blank=True,
126
+ help_text="Queue name (empty for all queues)"
127
+ )
128
+ registries = serializers.ListField(
129
+ child=serializers.ChoiceField(
130
+ choices=['failed', 'finished', 'deferred', 'scheduled']
131
+ ),
132
+ required=False,
133
+ default=list,
134
+ help_text="Registries to clean"
135
+ )
136
+ delete_demo_jobs_only = serializers.BooleanField(
137
+ default=True,
138
+ help_text="Only delete demo jobs (func starts with 'demo_')"
139
+ )
@@ -0,0 +1,58 @@
1
+ """
2
+ Worker information serializers for Django-RQ.
3
+ """
4
+
5
+ from rest_framework import serializers
6
+
7
+
8
+ class WorkerSerializer(serializers.Serializer):
9
+ """
10
+ Worker information serializer.
11
+
12
+ Provides detailed information about an RQ worker.
13
+ """
14
+
15
+ name = serializers.CharField(help_text="Worker name/ID")
16
+ queues = serializers.ListField(
17
+ child=serializers.CharField(), default=list, help_text="List of queue names"
18
+ )
19
+ state = serializers.CharField(help_text="Worker state (idle/busy/suspended)")
20
+ current_job = serializers.CharField(
21
+ allow_null=True, required=False, help_text="Current job ID if busy"
22
+ )
23
+ birth = serializers.DateTimeField(help_text="Worker start time")
24
+ last_heartbeat = serializers.DateTimeField(help_text="Last heartbeat timestamp")
25
+ successful_job_count = serializers.IntegerField(
26
+ default=0, help_text="Total successful jobs"
27
+ )
28
+ failed_job_count = serializers.IntegerField(default=0, help_text="Total failed jobs")
29
+ total_working_time = serializers.FloatField(
30
+ default=0.0, help_text="Total working time in seconds"
31
+ )
32
+
33
+
34
+ class WorkerStatsSerializer(serializers.Serializer):
35
+ """
36
+ Aggregated worker statistics serializer.
37
+
38
+ Provides overview of all workers across all queues.
39
+ """
40
+
41
+ total_workers = serializers.IntegerField(help_text="Total number of workers")
42
+ busy_workers = serializers.IntegerField(default=0, help_text="Number of busy workers")
43
+ idle_workers = serializers.IntegerField(default=0, help_text="Number of idle workers")
44
+ suspended_workers = serializers.IntegerField(
45
+ default=0, help_text="Number of suspended workers"
46
+ )
47
+ total_successful_jobs = serializers.IntegerField(
48
+ default=0, help_text="Total successful jobs (all workers)"
49
+ )
50
+ total_failed_jobs = serializers.IntegerField(
51
+ default=0, help_text="Total failed jobs (all workers)"
52
+ )
53
+ total_working_time = serializers.FloatField(
54
+ default=0.0, help_text="Total working time across all workers (seconds)"
55
+ )
56
+ workers = serializers.ListField(
57
+ child=WorkerSerializer(), default=list, help_text="List of individual workers"
58
+ )
@@ -0,0 +1,25 @@
1
+ """
2
+ Services for Django-RQ monitoring.
3
+ """
4
+
5
+ from .config_helper import (
6
+ get_redis_url,
7
+ get_rq_config,
8
+ is_prometheus_enabled,
9
+ is_rq_enabled,
10
+ register_schedules_from_config,
11
+ )
12
+ from .rq_converters import job_to_model, queue_to_model, worker_to_model
13
+
14
+ __all__ = [
15
+ # Converters
16
+ 'job_to_model',
17
+ 'queue_to_model',
18
+ 'worker_to_model',
19
+ # Config helpers
20
+ 'get_redis_url',
21
+ 'get_rq_config',
22
+ 'is_rq_enabled',
23
+ 'is_prometheus_enabled',
24
+ 'register_schedules_from_config',
25
+ ]
@@ -0,0 +1,233 @@
1
+ """
2
+ Helper functions for accessing Django-RQ configuration from django-cfg.
3
+
4
+ Provides utilities to get RQ config and check if RQ is enabled.
5
+ """
6
+
7
+ from typing import Optional
8
+
9
+ from django_cfg.modules.django_logging import get_logger
10
+
11
+ logger = get_logger("rq.config")
12
+
13
+
14
+ def get_rq_config() -> Optional["DjangoRQConfig"]:
15
+ """
16
+ Get Django-RQ configuration from django-cfg.
17
+
18
+ Returns:
19
+ DjangoRQConfig instance or None if not configured
20
+
21
+ Example:
22
+ >>> config = get_rq_config()
23
+ >>> if config and config.enabled:
24
+ >>> print(config.queues)
25
+ """
26
+ try:
27
+ from django_cfg.core.config import get_current_config
28
+ from django_cfg.models.django.django_rq import DjangoRQConfig
29
+
30
+ config = get_current_config()
31
+ if not config:
32
+ return None
33
+
34
+ django_rq = getattr(config, 'django_rq', None)
35
+
36
+ # Type validation
37
+ if django_rq and isinstance(django_rq, DjangoRQConfig):
38
+ return django_rq
39
+
40
+ return None
41
+
42
+ except Exception as e:
43
+ logger.debug(f"Failed to get RQ config: {e}")
44
+ return None
45
+
46
+
47
+ def is_rq_enabled() -> bool:
48
+ """
49
+ Check if Django-RQ is enabled in django-cfg.
50
+
51
+ Returns:
52
+ True if RQ is enabled, False otherwise
53
+
54
+ Example:
55
+ >>> if is_rq_enabled():
56
+ >>> from django_rq import enqueue
57
+ >>> enqueue(my_task)
58
+ """
59
+ config = get_rq_config()
60
+ if not config:
61
+ return False
62
+
63
+ return getattr(config, 'enabled', False)
64
+
65
+
66
+ def get_queue_names() -> list:
67
+ """
68
+ Get list of configured queue names.
69
+
70
+ Returns:
71
+ List of queue names from config
72
+
73
+ Example:
74
+ >>> queues = get_queue_names()
75
+ >>> print(queues) # ['default', 'high', 'low']
76
+ """
77
+ config = get_rq_config()
78
+ if not config:
79
+ return []
80
+
81
+ queues = getattr(config, 'queues', {})
82
+ if isinstance(queues, dict):
83
+ return list(queues.keys())
84
+
85
+ return []
86
+
87
+
88
+ def is_prometheus_enabled() -> bool:
89
+ """
90
+ Check if Prometheus metrics export is enabled.
91
+
92
+ Returns:
93
+ True if Prometheus is enabled, False otherwise
94
+ """
95
+ config = get_rq_config()
96
+ if not config:
97
+ return False
98
+
99
+ return getattr(config, 'prometheus_enabled', True)
100
+
101
+
102
+ def get_redis_url() -> Optional[str]:
103
+ """
104
+ Get Redis URL from django-cfg DjangoConfig.
105
+
106
+ This is the global Redis URL that is automatically used for:
107
+ - RQ queues (if queue.url is not set)
108
+ - RQ scheduler
109
+ - Cache backend
110
+ - Session backend
111
+
112
+ Returns:
113
+ Redis URL string (e.g., "redis://localhost:6379/0") or None
114
+
115
+ Example:
116
+ >>> redis_url = get_redis_url()
117
+ >>> print(redis_url) # redis://localhost:6379/0
118
+ """
119
+ try:
120
+ from django_cfg.core.config import get_current_config
121
+
122
+ config = get_current_config()
123
+ if not config:
124
+ return None
125
+
126
+ return getattr(config, 'redis_url', None)
127
+
128
+ except Exception as e:
129
+ logger.debug(f"Failed to get redis_url: {e}")
130
+ return None
131
+
132
+
133
+ def register_schedules_from_config():
134
+ """
135
+ Register scheduled jobs from django-cfg config in rq-scheduler.
136
+
137
+ This function should be called on Django startup (from AppConfig.ready()).
138
+ It reads schedules from config.django_rq.schedules and registers them
139
+ in rq-scheduler.
140
+
141
+ Example:
142
+ >>> from django_cfg.apps.rq.services import register_schedules_from_config
143
+ >>> register_schedules_from_config()
144
+ """
145
+ try:
146
+ import django_rq
147
+ from rq_scheduler import Scheduler
148
+
149
+ config = get_rq_config()
150
+ if not config or not config.enabled:
151
+ logger.debug("RQ not enabled, skipping schedule registration")
152
+ return
153
+
154
+ schedules = getattr(config, 'schedules', [])
155
+ if not schedules:
156
+ logger.debug("No schedules configured")
157
+ return
158
+
159
+ # Get scheduler for default queue
160
+ queue = django_rq.get_queue('default')
161
+ scheduler = Scheduler(queue=queue, connection=queue.connection)
162
+
163
+ logger.info(f"Registering {len(schedules)} scheduled jobs from config...")
164
+
165
+ for schedule_config in schedules:
166
+ try:
167
+ # Import function
168
+ func_path = schedule_config.func
169
+ module_path, func_name = func_path.rsplit('.', 1)
170
+
171
+ try:
172
+ import importlib
173
+ module = importlib.import_module(module_path)
174
+ func = getattr(module, func_name)
175
+ except (ImportError, AttributeError) as e:
176
+ logger.warning(f"Failed to import function {func_path}: {e}")
177
+ continue
178
+
179
+ # Get schedule type and register
180
+ if schedule_config.cron:
181
+ scheduler.cron(
182
+ schedule_config.cron,
183
+ func=func,
184
+ args=schedule_config.args,
185
+ kwargs=schedule_config.kwargs,
186
+ queue_name=schedule_config.queue,
187
+ timeout=schedule_config.timeout,
188
+ result_ttl=schedule_config.result_ttl,
189
+ id=schedule_config.job_id,
190
+ repeat=schedule_config.repeat,
191
+ )
192
+ logger.info(f"✓ Registered cron schedule: {func_path} ({schedule_config.cron})")
193
+
194
+ elif schedule_config.interval:
195
+ from datetime import datetime
196
+ scheduler.schedule(
197
+ scheduled_time=datetime.utcnow(), # Start immediately
198
+ func=func,
199
+ args=schedule_config.args,
200
+ kwargs=schedule_config.kwargs,
201
+ interval=schedule_config.interval,
202
+ queue_name=schedule_config.queue,
203
+ timeout=schedule_config.timeout,
204
+ result_ttl=schedule_config.result_ttl,
205
+ id=schedule_config.job_id,
206
+ repeat=schedule_config.repeat,
207
+ )
208
+ logger.info(f"✓ Registered interval schedule: {func_path} (every {schedule_config.interval}s)")
209
+
210
+ elif schedule_config.scheduled_time:
211
+ from datetime import datetime
212
+ scheduled_dt = datetime.fromisoformat(schedule_config.scheduled_time)
213
+
214
+ scheduler.schedule(
215
+ scheduled_time=scheduled_dt,
216
+ func=func,
217
+ args=schedule_config.args,
218
+ kwargs=schedule_config.kwargs,
219
+ queue_name=schedule_config.queue,
220
+ timeout=schedule_config.timeout,
221
+ result_ttl=schedule_config.result_ttl,
222
+ id=schedule_config.job_id,
223
+ )
224
+ logger.info(f"✓ Registered one-time schedule: {func_path} (at {schedule_config.scheduled_time})")
225
+
226
+ except Exception as e:
227
+ logger.error(f"Failed to register schedule {schedule_config.func}: {e}")
228
+ continue
229
+
230
+ logger.info("Schedule registration completed")
231
+
232
+ except Exception as e:
233
+ logger.error(f"Failed to register schedules: {e}", exc_info=True)