django-cfg 1.1.53__py3-none-any.whl → 1.1.55__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.
Files changed (49) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/accounts/migrations/0004_delete_twilioresponse.py +16 -0
  3. django_cfg/apps/tasks/admin.py +5 -6
  4. django_cfg/apps/tasks/serializers.py +82 -0
  5. django_cfg/apps/tasks/static/tasks/css/dashboard.css +269 -0
  6. django_cfg/apps/tasks/static/tasks/js/api.js +144 -0
  7. django_cfg/apps/tasks/static/tasks/js/dashboard.js +614 -0
  8. django_cfg/apps/tasks/static/tasks/js/modals.js +452 -0
  9. django_cfg/apps/tasks/static/tasks/js/notifications.js +144 -0
  10. django_cfg/apps/tasks/static/tasks/js/task-monitor.js +454 -0
  11. django_cfg/apps/tasks/static/tasks/js/theme.js +77 -0
  12. django_cfg/apps/tasks/templates/tasks/base.html +96 -0
  13. django_cfg/apps/tasks/templates/tasks/components/info_cards.html +85 -0
  14. django_cfg/apps/tasks/templates/tasks/components/management_actions.html +53 -0
  15. django_cfg/apps/tasks/templates/tasks/components/overview_tab.html +22 -0
  16. django_cfg/apps/tasks/templates/tasks/components/queues_tab.html +19 -0
  17. django_cfg/apps/tasks/templates/tasks/components/status_cards.html +50 -0
  18. django_cfg/apps/tasks/templates/tasks/components/tab_navigation.html +27 -0
  19. django_cfg/apps/tasks/templates/tasks/components/task_details_modal.html +103 -0
  20. django_cfg/apps/tasks/templates/tasks/components/tasks_tab.html +32 -0
  21. django_cfg/apps/tasks/templates/tasks/components/workers_tab.html +29 -0
  22. django_cfg/apps/tasks/templates/tasks/dashboard.html +22 -442
  23. django_cfg/apps/tasks/urls.py +10 -8
  24. django_cfg/apps/tasks/views.py +410 -172
  25. django_cfg/archive/django_sample.zip +0 -0
  26. django_cfg/core/generation.py +26 -2
  27. django_cfg/management/commands/clear_constance.py +200 -0
  28. django_cfg/management/commands/rundramatiq.py +6 -3
  29. django_cfg/models/constance.py +29 -23
  30. django_cfg/modules/base.py +19 -1
  31. django_cfg/modules/django_llm/README.md +296 -432
  32. django_cfg/modules/django_llm/__init__.py +5 -9
  33. django_cfg/modules/django_llm/llm/client.py +195 -176
  34. django_cfg/modules/django_llm/llm/costs.py +188 -0
  35. django_cfg/modules/django_llm/llm/extractor.py +85 -0
  36. django_cfg/modules/django_llm/llm/tokenizer.py +83 -0
  37. django_cfg/modules/django_tasks.py +282 -53
  38. django_cfg/routers.py +51 -16
  39. django_cfg/utils/smart_defaults.py +17 -1
  40. {django_cfg-1.1.53.dist-info → django_cfg-1.1.55.dist-info}/METADATA +6 -1
  41. {django_cfg-1.1.53.dist-info → django_cfg-1.1.55.dist-info}/RECORD +44 -26
  42. django_cfg/examples/README_NGROK.md +0 -186
  43. django_cfg/examples/README_NGROK_ENV.md +0 -194
  44. django_cfg/examples/ngrok_env_example.py +0 -155
  45. django_cfg/examples/ngrok_example.py +0 -75
  46. django_cfg/modules/django_llm/service.py +0 -423
  47. {django_cfg-1.1.53.dist-info → django_cfg-1.1.55.dist-info}/WHEEL +0 -0
  48. {django_cfg-1.1.53.dist-info → django_cfg-1.1.55.dist-info}/entry_points.txt +0 -0
  49. {django_cfg-1.1.53.dist-info → django_cfg-1.1.55.dist-info}/licenses/LICENSE +0 -0
django_cfg/__init__.py CHANGED
@@ -38,7 +38,7 @@ default_app_config = "django_cfg.apps.DjangoCfgConfig"
38
38
  from typing import TYPE_CHECKING
39
39
 
40
40
  # Version information
41
- __version__ = "1.1.53"
41
+ __version__ = "1.1.55"
42
42
  __author__ = "Unrealos Team"
43
43
  __email__ = "info@unrealos.com"
44
44
  __license__ = "MIT"
@@ -0,0 +1,16 @@
1
+ # Generated by Django 5.2.6 on 2025-09-16 18:57
2
+
3
+ from django.db import migrations
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ('django_cfg_accounts', '0003_twilioresponse'),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.DeleteModel(
14
+ name='TwilioResponse',
15
+ ),
16
+ ]
@@ -10,6 +10,7 @@ import subprocess
10
10
  import sys
11
11
  from typing import Dict, Any
12
12
 
13
+ from django.db.models import Count
13
14
  from django.contrib import admin
14
15
  from django.contrib.admin.views.main import ChangeList
15
16
  from django.http import JsonResponse
@@ -226,8 +227,8 @@ class TaskQueueAdmin(ModelAdmin):
226
227
  queues_info = {}
227
228
  config = self.tasks_service.config
228
229
 
229
- if config and config.queues:
230
- for queue_name in config.queues:
230
+ if config and config.dramatiq and config.dramatiq.queues:
231
+ for queue_name in config.dramatiq.queues:
231
232
  queue_key = f"dramatiq:default.DQ.{queue_name}"
232
233
  queue_length = redis_client.llen(queue_key)
233
234
 
@@ -267,17 +268,15 @@ class TaskQueueAdmin(ModelAdmin):
267
268
  if not Task:
268
269
  return {'error': 'django_dramatiq not available'}
269
270
 
270
- # Get task counts by status
271
- from django.db.models import Count
272
271
 
273
- stats = Task.objects.aggregate(
272
+ stats = Task.tasks.aggregate(
274
273
  total=Count('id'),
275
274
  # Add more aggregations as needed
276
275
  )
277
276
 
278
277
  # Get recent tasks
279
278
  recent_tasks = list(
280
- Task.objects.order_by('-created_at')[:10]
279
+ Task.tasks.order_by('-created_at')[:10]
281
280
  .values('actor_name', 'status', 'created_at', 'updated_at')
282
281
  )
283
282
 
@@ -0,0 +1,82 @@
1
+ """
2
+ Serializers for Django CFG Tasks app.
3
+
4
+ Provides DRF serializers for task management API endpoints.
5
+ """
6
+
7
+ from rest_framework import serializers
8
+ from typing import Dict, Any
9
+
10
+
11
+ class QueueStatusSerializer(serializers.Serializer):
12
+ """Serializer for queue status data."""
13
+
14
+ queues = serializers.DictField(
15
+ child=serializers.DictField(
16
+ child=serializers.IntegerField()
17
+ ),
18
+ help_text="Queue information with pending/failed counts"
19
+ )
20
+ workers = serializers.IntegerField(help_text="Number of active workers")
21
+ redis_connected = serializers.BooleanField(help_text="Redis connection status")
22
+ timestamp = serializers.CharField(help_text="Current timestamp")
23
+ error = serializers.CharField(required=False, help_text="Error message if any")
24
+
25
+
26
+ class TaskStatisticsSerializer(serializers.Serializer):
27
+ """Serializer for task statistics data."""
28
+
29
+ statistics = serializers.DictField(
30
+ child=serializers.IntegerField(),
31
+ help_text="Task count statistics"
32
+ )
33
+ recent_tasks = serializers.ListField(
34
+ child=serializers.DictField(),
35
+ help_text="List of recent tasks"
36
+ )
37
+ timestamp = serializers.CharField(help_text="Current timestamp")
38
+ error = serializers.CharField(required=False, help_text="Error message if any")
39
+
40
+
41
+ class WorkerActionSerializer(serializers.Serializer):
42
+ """Serializer for worker management actions."""
43
+
44
+ action = serializers.ChoiceField(
45
+ choices=['start', 'stop', 'restart'],
46
+ help_text="Action to perform on workers"
47
+ )
48
+ processes = serializers.IntegerField(
49
+ default=1,
50
+ min_value=1,
51
+ max_value=10,
52
+ help_text="Number of worker processes"
53
+ )
54
+ threads = serializers.IntegerField(
55
+ default=2,
56
+ min_value=1,
57
+ max_value=20,
58
+ help_text="Number of threads per process"
59
+ )
60
+
61
+
62
+ class QueueActionSerializer(serializers.Serializer):
63
+ """Serializer for queue management actions."""
64
+
65
+ action = serializers.ChoiceField(
66
+ choices=['clear', 'clear_all', 'purge', 'purge_failed', 'flush'],
67
+ help_text="Action to perform on queues"
68
+ )
69
+ queue_names = serializers.ListField(
70
+ child=serializers.CharField(),
71
+ required=False,
72
+ help_text="Specific queues to target (empty = all queues)"
73
+ )
74
+
75
+
76
+ class APIResponseSerializer(serializers.Serializer):
77
+ """Standard API response serializer."""
78
+
79
+ success = serializers.BooleanField(help_text="Operation success status")
80
+ message = serializers.CharField(required=False, help_text="Success message")
81
+ error = serializers.CharField(required=False, help_text="Error message")
82
+ data = serializers.DictField(required=False, help_text="Response data")
@@ -0,0 +1,269 @@
1
+ /**
2
+ * Dashboard Custom Styles
3
+ * Additional styles for the Dramatiq Tasks Dashboard
4
+ */
5
+
6
+ /* Custom scrollbar for dark mode */
7
+ ::-webkit-scrollbar {
8
+ width: 8px;
9
+ height: 8px;
10
+ }
11
+
12
+ ::-webkit-scrollbar-track {
13
+ background: transparent;
14
+ }
15
+
16
+ ::-webkit-scrollbar-thumb {
17
+ background: rgba(156, 163, 175, 0.5);
18
+ border-radius: 4px;
19
+ }
20
+
21
+ ::-webkit-scrollbar-thumb:hover {
22
+ background: rgba(156, 163, 175, 0.7);
23
+ }
24
+
25
+ .dark ::-webkit-scrollbar-thumb {
26
+ background: rgba(75, 85, 99, 0.5);
27
+ }
28
+
29
+ .dark ::-webkit-scrollbar-thumb:hover {
30
+ background: rgba(75, 85, 99, 0.7);
31
+ }
32
+
33
+ /* Tab transitions */
34
+ .tab-button {
35
+ transition: all 0.2s ease-in-out;
36
+ }
37
+
38
+ .tab-panel {
39
+ transition: opacity 0.2s ease-in-out;
40
+ }
41
+
42
+ .tab-panel.hidden {
43
+ opacity: 0;
44
+ }
45
+
46
+ .tab-panel.active {
47
+ opacity: 1;
48
+ }
49
+
50
+ /* Loading animations */
51
+ @keyframes pulse {
52
+ 0%, 100% {
53
+ opacity: 1;
54
+ }
55
+ 50% {
56
+ opacity: 0.5;
57
+ }
58
+ }
59
+
60
+ .animate-pulse {
61
+ animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
62
+ }
63
+
64
+ /* Status indicators */
65
+ .status-indicator {
66
+ position: relative;
67
+ display: inline-block;
68
+ }
69
+
70
+ .status-indicator::before {
71
+ content: '';
72
+ position: absolute;
73
+ top: 0;
74
+ right: 0;
75
+ width: 8px;
76
+ height: 8px;
77
+ border-radius: 50%;
78
+ border: 2px solid white;
79
+ }
80
+
81
+ .status-indicator.online::before {
82
+ background-color: #10b981;
83
+ }
84
+
85
+ .status-indicator.offline::before {
86
+ background-color: #ef4444;
87
+ }
88
+
89
+ .status-indicator.warning::before {
90
+ background-color: #f59e0b;
91
+ }
92
+
93
+ /* Card hover effects */
94
+ .card-hover {
95
+ transition: all 0.2s ease-in-out;
96
+ }
97
+
98
+ .card-hover:hover {
99
+ transform: translateY(-2px);
100
+ box-shadow: 0 10px 25px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
101
+ }
102
+
103
+ .dark .card-hover:hover {
104
+ box-shadow: 0 10px 25px -3px rgba(0, 0, 0, 0.3), 0 4px 6px -2px rgba(0, 0, 0, 0.2);
105
+ }
106
+
107
+ /* Button loading state */
108
+ .btn-loading {
109
+ position: relative;
110
+ pointer-events: none;
111
+ }
112
+
113
+ .btn-loading::after {
114
+ content: '';
115
+ position: absolute;
116
+ top: 50%;
117
+ left: 50%;
118
+ width: 16px;
119
+ height: 16px;
120
+ margin: -8px 0 0 -8px;
121
+ border: 2px solid transparent;
122
+ border-top: 2px solid currentColor;
123
+ border-radius: 50%;
124
+ animation: spin 1s linear infinite;
125
+ }
126
+
127
+ @keyframes spin {
128
+ 0% {
129
+ transform: rotate(0deg);
130
+ }
131
+ 100% {
132
+ transform: rotate(360deg);
133
+ }
134
+ }
135
+
136
+ /* Modal backdrop blur */
137
+ .modal-backdrop {
138
+ backdrop-filter: blur(4px);
139
+ -webkit-backdrop-filter: blur(4px);
140
+ }
141
+
142
+ /* Custom focus styles */
143
+ .focus-ring:focus {
144
+ outline: 2px solid transparent;
145
+ outline-offset: 2px;
146
+ box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.5);
147
+ }
148
+
149
+ .dark .focus-ring:focus {
150
+ box-shadow: 0 0 0 2px rgba(96, 165, 250, 0.5);
151
+ }
152
+
153
+ /* Progress bars */
154
+ .progress-bar {
155
+ position: relative;
156
+ overflow: hidden;
157
+ background-color: #e5e7eb;
158
+ border-radius: 0.25rem;
159
+ height: 0.5rem;
160
+ }
161
+
162
+ .dark .progress-bar {
163
+ background-color: #374151;
164
+ }
165
+
166
+ .progress-bar-fill {
167
+ height: 100%;
168
+ background-color: #3b82f6;
169
+ transition: width 0.3s ease-in-out;
170
+ }
171
+
172
+ .progress-bar-fill.success {
173
+ background-color: #10b981;
174
+ }
175
+
176
+ .progress-bar-fill.warning {
177
+ background-color: #f59e0b;
178
+ }
179
+
180
+ .progress-bar-fill.error {
181
+ background-color: #ef4444;
182
+ }
183
+
184
+ /* Tooltip styles */
185
+ .tooltip {
186
+ position: relative;
187
+ }
188
+
189
+ .tooltip::before {
190
+ content: attr(data-tooltip);
191
+ position: absolute;
192
+ bottom: 100%;
193
+ left: 50%;
194
+ transform: translateX(-50%);
195
+ padding: 0.5rem;
196
+ background-color: #1f2937;
197
+ color: white;
198
+ font-size: 0.75rem;
199
+ border-radius: 0.25rem;
200
+ white-space: nowrap;
201
+ opacity: 0;
202
+ pointer-events: none;
203
+ transition: opacity 0.2s ease-in-out;
204
+ z-index: 1000;
205
+ }
206
+
207
+ .tooltip:hover::before {
208
+ opacity: 1;
209
+ }
210
+
211
+ /* Responsive adjustments */
212
+ @media (max-width: 768px) {
213
+ .tab-button {
214
+ padding: 0.5rem 0.25rem;
215
+ font-size: 0.75rem;
216
+ }
217
+
218
+ .tab-button .material-icons {
219
+ display: none;
220
+ }
221
+
222
+ .status-cards {
223
+ grid-template-columns: repeat(2, 1fr);
224
+ gap: 1rem;
225
+ }
226
+
227
+ .info-cards {
228
+ grid-template-columns: 1fr;
229
+ }
230
+ }
231
+
232
+ @media (max-width: 640px) {
233
+ .status-cards {
234
+ grid-template-columns: 1fr;
235
+ }
236
+
237
+ .tab-navigation {
238
+ overflow-x: auto;
239
+ scrollbar-width: none;
240
+ -ms-overflow-style: none;
241
+ }
242
+
243
+ .tab-navigation::-webkit-scrollbar {
244
+ display: none;
245
+ }
246
+ }
247
+
248
+ /* Print styles */
249
+ @media print {
250
+ .tab-navigation,
251
+ .modal-container,
252
+ button {
253
+ display: none !important;
254
+ }
255
+
256
+ .tab-panel {
257
+ display: block !important;
258
+ }
259
+
260
+ .bg-gray-50,
261
+ .dark\:bg-gray-900 {
262
+ background: white !important;
263
+ }
264
+
265
+ .text-gray-900,
266
+ .dark\:text-white {
267
+ color: black !important;
268
+ }
269
+ }
@@ -0,0 +1,144 @@
1
+ /**
2
+ * API Client for Dramatiq Tasks Dashboard
3
+ * Handles all API communication with the backend
4
+ */
5
+
6
+ class TasksAPI {
7
+ constructor(baseUrl = '/cfg/tasks/api') {
8
+ this.baseUrl = baseUrl;
9
+ }
10
+
11
+ /**
12
+ * Make an API request
13
+ * @param {string} endpoint - API endpoint
14
+ * @param {Object} options - Fetch options
15
+ * @returns {Promise<Object>} API response
16
+ */
17
+ async request(endpoint, options = {}) {
18
+ const url = `${this.baseUrl}${endpoint}`;
19
+ const defaultOptions = {
20
+ headers: {
21
+ 'Content-Type': 'application/json',
22
+ 'X-Requested-With': 'XMLHttpRequest',
23
+ },
24
+ credentials: 'same-origin', // Include cookies for session authentication
25
+ };
26
+
27
+ // Add CSRF token if available
28
+ let csrfToken = document.querySelector('[name=csrfmiddlewaretoken]');
29
+ if (!csrfToken) {
30
+ // Try alternative selectors
31
+ csrfToken = document.querySelector('input[name="csrfmiddlewaretoken"]');
32
+ }
33
+ if (!csrfToken) {
34
+ // Try getting from cookie
35
+ const cookies = document.cookie.split(';');
36
+ for (let cookie of cookies) {
37
+ const [name, value] = cookie.trim().split('=');
38
+ if (name === 'csrftoken') {
39
+ defaultOptions.headers['X-CSRFToken'] = value;
40
+ break;
41
+ }
42
+ }
43
+ } else {
44
+ defaultOptions.headers['X-CSRFToken'] = csrfToken.value;
45
+ }
46
+
47
+ try {
48
+ const response = await fetch(url, { ...defaultOptions, ...options });
49
+
50
+ if (!response.ok) {
51
+ throw new Error(`HTTP error! status: ${response.status}`);
52
+ }
53
+
54
+ return await response.json();
55
+ } catch (error) {
56
+ console.error('API request failed:', error);
57
+ throw error;
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Get queue status
63
+ * @returns {Promise<Object>} Queue status data
64
+ */
65
+ async getQueueStatus() {
66
+ return this.request('/queues/status/');
67
+ }
68
+
69
+ /**
70
+ * Get task statistics
71
+ * @returns {Promise<Object>} Task statistics data
72
+ */
73
+ async getTaskStatistics() {
74
+ return this.request('/tasks/stats/');
75
+ }
76
+
77
+ /**
78
+ * Get detailed task list
79
+ * @param {Object} params - Query parameters (status, queue, search, limit, offset)
80
+ * @returns {Promise<Object>} Task list
81
+ */
82
+ async getTaskList(params = {}) {
83
+ const queryString = new URLSearchParams(params).toString();
84
+ const url = `/tasks/list/${queryString ? '?' + queryString : ''}`;
85
+ return this.request(url);
86
+ }
87
+
88
+ /**
89
+ * Start workers
90
+ * @param {Object} config - Worker configuration
91
+ * @returns {Promise<Object>} Operation result
92
+ */
93
+ async startWorkers(config = {}) {
94
+ return this.request('/workers/manage/', {
95
+ method: 'POST',
96
+ body: JSON.stringify({
97
+ action: 'start',
98
+ ...config
99
+ })
100
+ });
101
+ }
102
+
103
+ /**
104
+ * Stop workers
105
+ * @returns {Promise<Object>} Operation result
106
+ */
107
+ async stopWorkers() {
108
+ return this.request('/workers/manage/', {
109
+ method: 'POST',
110
+ body: JSON.stringify({
111
+ action: 'stop'
112
+ })
113
+ });
114
+ }
115
+
116
+ /**
117
+ * Clear all queues
118
+ * @returns {Promise<Object>} Operation result
119
+ */
120
+ async clearQueues() {
121
+ return this.request('/queues/manage/', {
122
+ method: 'POST',
123
+ body: JSON.stringify({
124
+ action: 'clear_all'
125
+ })
126
+ });
127
+ }
128
+
129
+ /**
130
+ * Purge failed tasks
131
+ * @returns {Promise<Object>} Operation result
132
+ */
133
+ async purgeFailedTasks() {
134
+ return this.request('/queues/manage/', {
135
+ method: 'POST',
136
+ body: JSON.stringify({
137
+ action: 'purge_failed'
138
+ })
139
+ });
140
+ }
141
+ }
142
+
143
+ // Create global API instance
144
+ window.tasksAPI = new TasksAPI();