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
django_cfg/__init__.py CHANGED
@@ -32,7 +32,7 @@ Example:
32
32
  default_app_config = "django_cfg.apps.DjangoCfgConfig"
33
33
 
34
34
  # Version information
35
- __version__ = "1.5.1"
35
+ __version__ = "1.5.3"
36
36
  __license__ = "MIT"
37
37
 
38
38
  # Import registry for organized lazy loading
@@ -0,0 +1,73 @@
1
+ # PostgreSQL Transaction Error Fix
2
+
3
+ ## Problem
4
+
5
+ Error: **"current transaction is aborted, commands ignored until end of transaction block"**
6
+
7
+ ### Root Causes:
8
+ 1. Using `.extra()` for raw SQL queries in PostgreSQL
9
+ 2. Calling `.count()` on models whose tables don't exist in the database
10
+
11
+ ## Solution
12
+
13
+ ### 1. Replaced `.extra()` with `TruncDate()` in `charts_service.py`
14
+
15
+ **Before:**
16
+ ```python
17
+ .extra({'date': "date(date_joined)"}) # ❌ Raw SQL
18
+ ```
19
+
20
+ **After:**
21
+ ```python
22
+ from django.db.models.functions import TruncDate
23
+ .annotate(date=TruncDate('date_joined')) # ✅ Django ORM
24
+ ```
25
+
26
+ ### 2. Added table existence check in `statistics_service.py`
27
+
28
+ **Problem:** `model.objects.count()` was called on ALL models, including those without database tables (e.g., migrations not run).
29
+
30
+ **Solution:** Check if table exists before querying:
31
+
32
+ ```python
33
+ def _get_model_stats(self, model) -> Optional[Dict[str, Any]]:
34
+ try:
35
+ from django.db import connection, OperationalError, ProgrammingError
36
+
37
+ # Check if table exists before querying
38
+ table_name = model._meta.db_table
39
+ with connection.cursor() as cursor:
40
+ cursor.execute(f"SELECT COUNT(*) FROM {connection.ops.quote_name(table_name)} LIMIT 1")
41
+
42
+ # Now safe to call model.objects.count()
43
+ model_stats = {
44
+ "count": model.objects.count(),
45
+ ...
46
+ }
47
+ return model_stats
48
+
49
+ except (OperationalError, ProgrammingError):
50
+ # Table doesn't exist - skip this model
51
+ return None
52
+ ```
53
+
54
+ ## Modified Files
55
+
56
+ - ✅ `services/charts_service.py` - replaced all `.extra()` with `TruncDate()`
57
+ - ✅ `services/statistics_service.py` - added table existence check
58
+ - ✅ `views/overview_views.py` - simplified, removed complex transaction logic
59
+
60
+ ## Key Lessons
61
+
62
+ 1. **Don't use `.extra()`** - always use Django ORM functions
63
+ 2. **Check table existence** before querying models
64
+ 3. **Don't overcomplicate** - let Django handle transactions
65
+ 4. **Catch specific exceptions** - `OperationalError`, `ProgrammingError` for DB issues
66
+
67
+ ## Conclusion
68
+
69
+ Keep it simple! The issue was two simple bugs:
70
+ - Using raw SQL (`.extra()`)
71
+ - Querying non-existent tables
72
+
73
+ Fixed by using proper Django ORM and checking table existence.
@@ -22,12 +22,6 @@ from .commands import (
22
22
  CommandHelpResponseSerializer,
23
23
  )
24
24
  from .apizones import APIZoneSerializer, APIZonesSummarySerializer
25
- from .django_q2 import (
26
- DjangoQ2ScheduleSerializer,
27
- DjangoQ2TaskSerializer,
28
- DjangoQ2StatusSerializer,
29
- DjangoQ2SummarySerializer,
30
- )
31
25
 
32
26
  __all__ = [
33
27
  # Base
@@ -65,10 +59,4 @@ __all__ = [
65
59
  # API Zones
66
60
  'APIZoneSerializer',
67
61
  'APIZonesSummarySerializer',
68
-
69
- # Django-Q2
70
- 'DjangoQ2ScheduleSerializer',
71
- 'DjangoQ2TaskSerializer',
72
- 'DjangoQ2StatusSerializer',
73
- 'DjangoQ2SummarySerializer',
74
62
  ]
@@ -19,7 +19,7 @@ class QuickActionSerializer(serializers.Serializer):
19
19
  icon = serializers.CharField(help_text="Material icon name")
20
20
  link = serializers.CharField(help_text="Action URL")
21
21
  color = serializers.ChoiceField(
22
- choices=['primary', 'success', 'warning', 'danger', 'secondary'],
22
+ choices=['primary', 'success', 'warning', 'danger', 'secondary', 'info', 'default'],
23
23
  default='primary',
24
24
  help_text="Button color theme"
25
25
  )
@@ -11,7 +11,6 @@ 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 .django_q2_service import DjangoQ2Service
15
14
 
16
15
  __all__ = [
17
16
  'StatisticsService',
@@ -20,5 +19,4 @@ __all__ = [
20
19
  'CommandsService',
21
20
  'APIZonesService',
22
21
  'OverviewService',
23
- 'DjangoQ2Service',
24
22
  ]
@@ -11,6 +11,7 @@ from typing import Any, Dict, List
11
11
 
12
12
  from django.contrib.auth import get_user_model
13
13
  from django.db.models import Count
14
+ from django.db.models.functions import TruncDate
14
15
  from django.utils import timezone
15
16
 
16
17
  logger = logging.getLogger(__name__)
@@ -78,7 +79,7 @@ class ChartsService:
78
79
  # Get registration counts by date
79
80
  registration_data = (
80
81
  User.objects.filter(date_joined__date__gte=start_date)
81
- .extra({'date': "date(date_joined)"})
82
+ .annotate(date=TruncDate('date_joined'))
82
83
  .values('date')
83
84
  .annotate(count=Count('id'))
84
85
  .order_by('date')
@@ -138,7 +139,7 @@ class ChartsService:
138
139
  # Get login activity (users who logged in each day)
139
140
  activity_data = (
140
141
  User.objects.filter(last_login__date__gte=start_date, last_login__isnull=False)
141
- .extra({'date': "date(last_login)"})
142
+ .annotate(date=TruncDate('last_login'))
142
143
  .values('date')
143
144
  .annotate(count=Count('id'))
144
145
  .order_by('date')
@@ -192,7 +193,7 @@ class ChartsService:
192
193
  # Get activity data by date
193
194
  activity_data = (
194
195
  User.objects.filter(last_login__date__gte=start_date, last_login__isnull=False)
195
- .extra({'date': "date(last_login)"})
196
+ .annotate(date=TruncDate('last_login'))
196
197
  .values('date')
197
198
  .annotate(count=Count('id'))
198
199
  .order_by('date')
@@ -64,7 +64,7 @@ class StatisticsService:
64
64
  'superusers': superusers,
65
65
  }
66
66
  except Exception as e:
67
- self.logger.error(f"Error getting user statistics: {e}")
67
+ self.logger.error(f"Error getting user statistics: {e}", exc_info=True)
68
68
  return {
69
69
  'total_users': 0,
70
70
  'active_users': 0,
@@ -145,20 +145,29 @@ class StatisticsService:
145
145
 
146
146
  def _get_model_stats(self, model) -> Optional[Dict[str, Any]]:
147
147
  """Get statistics for a specific model."""
148
+ from django.db import OperationalError, ProgrammingError
149
+
148
150
  try:
151
+ # Just try to count - if table doesn't exist, exception will be caught
152
+ count = model.objects.count()
153
+
149
154
  # Get basic model info
150
155
  model_stats = {
151
156
  "name": model._meta.verbose_name_plural
152
157
  or model._meta.verbose_name
153
158
  or model._meta.model_name,
154
- "count": model.objects.count(),
159
+ "count": count,
155
160
  "fields_count": len(model._meta.fields),
156
161
  "admin_url": f"admin:{model._meta.app_label}_{model._meta.model_name}_changelist",
157
162
  }
158
163
 
159
164
  return model_stats
160
165
 
166
+ except (OperationalError, ProgrammingError):
167
+ # Table doesn't exist or other DB error - skip this model silently
168
+ return None
161
169
  except Exception:
170
+ # Any other error - skip this model silently
162
171
  return None
163
172
 
164
173
  def get_stat_cards(self) -> List[Dict[str, Any]]:
@@ -9,6 +9,7 @@ import logging
9
9
  from datetime import datetime
10
10
  from typing import Any, Dict, List, Literal
11
11
 
12
+
12
13
  logger = logging.getLogger(__name__)
13
14
 
14
15
 
@@ -105,7 +106,7 @@ class SystemHealthService:
105
106
 
106
107
  def check_queue_health(self) -> Dict[str, Any]:
107
108
  """
108
- Check task queue (ReArq) health via Redis connection.
109
+ Check task queue (Django-RQ) health via Redis connection.
109
110
 
110
111
  Returns:
111
112
  Health status dictionary
@@ -191,90 +192,6 @@ class SystemHealthService:
191
192
  'health_percentage': 0,
192
193
  }
193
194
 
194
- def check_django_q2_health(self) -> Dict[str, Any]:
195
- """
196
- Check Django-Q2 task scheduling configuration and status.
197
-
198
- Returns:
199
- Health status dictionary with schedule count and cluster status
200
- """
201
- try:
202
- from django_cfg.core.config import get_current_config
203
-
204
- config = get_current_config()
205
-
206
- # Check if django_q2 is configured
207
- if not hasattr(config, 'django_q2') or not config.django_q2:
208
- return {
209
- 'component': 'django_q2',
210
- 'status': 'info',
211
- 'description': 'Django-Q2 scheduling not configured',
212
- 'last_check': datetime.now().isoformat(),
213
- 'health_percentage': 100,
214
- 'details': {
215
- 'enabled': False,
216
- 'schedules_count': 0,
217
- }
218
- }
219
-
220
- django_q2_config = config.django_q2
221
-
222
- # Check if enabled
223
- if not django_q2_config.enabled:
224
- return {
225
- 'component': 'django_q2',
226
- 'status': 'warning',
227
- 'description': 'Django-Q2 scheduling is disabled',
228
- 'last_check': datetime.now().isoformat(),
229
- 'health_percentage': 50,
230
- 'details': {
231
- 'enabled': False,
232
- 'schedules_count': len(django_q2_config.schedules) if django_q2_config.schedules else 0,
233
- }
234
- }
235
-
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
253
-
254
- return {
255
- 'component': 'django_q2',
256
- 'status': 'healthy',
257
- 'description': f'{schedules_count} schedule(s) configured, cluster {"running" if cluster_running else "idle"}',
258
- 'last_check': datetime.now().isoformat(),
259
- 'health_percentage': 100,
260
- 'details': {
261
- 'enabled': True,
262
- 'schedules_count': schedules_count,
263
- 'cluster_running': cluster_running,
264
- 'workers': django_q2_config.workers,
265
- }
266
- }
267
-
268
- except Exception as e:
269
- self.logger.error(f"Django-Q2 health check failed: {e}")
270
- return {
271
- 'component': 'django_q2',
272
- 'status': 'error',
273
- 'description': f'Django-Q2 check error: {str(e)}',
274
- 'last_check': datetime.now().isoformat(),
275
- 'health_percentage': 0,
276
- }
277
-
278
195
  def get_all_health_checks(self) -> List[Dict[str, Any]]:
279
196
  """
280
197
  Run all health checks and return aggregated results.
@@ -289,7 +206,6 @@ class SystemHealthService:
289
206
  self.check_cache_health(),
290
207
  self.check_queue_health(),
291
208
  self.check_storage_health(),
292
- self.check_django_q2_health(),
293
209
  ]
294
210
 
295
211
  return checks
@@ -328,42 +244,84 @@ class SystemHealthService:
328
244
  Get quick action buttons for dashboard.
329
245
 
330
246
  Returns:
331
- List of quick action dictionaries
247
+ List of quick action dictionaries with safe URL resolution
332
248
 
333
- %%AI_HINT: Actions link to admin pages or trigger common tasks%%
249
+ %%AI_HINT: Actions link to admin pages or API endpoints%%
334
250
  """
251
+ from django.urls import reverse, NoReverseMatch
252
+
253
+ def safe_reverse(url_name: str, fallback: str) -> str:
254
+ """Safely reverse URL with fallback."""
255
+ try:
256
+ return reverse(url_name)
257
+ except NoReverseMatch:
258
+ self.logger.debug(f"Could not reverse URL '{url_name}', using fallback: {fallback}")
259
+ return fallback
260
+
335
261
  actions = [
262
+ {
263
+ 'title': 'Admin Panel',
264
+ 'description': 'Open Django admin interface',
265
+ 'icon': 'admin_panel_settings',
266
+ 'link': safe_reverse('admin:index', '/admin/'),
267
+ 'color': 'primary',
268
+ 'category': 'admin',
269
+ },
336
270
  {
337
271
  'title': 'User Management',
338
272
  'description': 'Manage users and permissions',
339
273
  'icon': 'people',
340
- 'link': '/admin/auth/user/',
274
+ 'link': safe_reverse('admin:auth_user_changelist', '/admin/auth/user/'),
341
275
  'color': 'primary',
342
276
  'category': 'admin',
343
277
  },
344
278
  {
345
- 'title': 'View Logs',
346
- 'description': 'Check system logs',
347
- 'icon': 'description',
348
- 'link': '/admin/django_cfg/logs/',
279
+ 'title': 'Settings',
280
+ 'description': 'Configure application settings',
281
+ 'icon': 'settings',
282
+ 'link': safe_reverse('admin:constance_config_changelist', '/admin/constance/config/'),
283
+ 'color': 'default',
284
+ 'category': 'admin',
285
+ },
286
+ {
287
+ 'title': 'System Health',
288
+ 'description': 'Check system health status',
289
+ 'icon': 'favorite',
290
+ 'link': '/cfg/dashboard/api/system/health/',
291
+ 'color': 'success',
292
+ 'category': 'monitoring',
293
+ },
294
+ {
295
+ 'title': 'RQ Workers',
296
+ 'description': 'Monitor task queue workers',
297
+ 'icon': 'work',
298
+ 'link': '/cfg/rq/workers/',
299
+ 'color': 'info',
300
+ 'category': 'monitoring',
301
+ },
302
+ {
303
+ 'title': 'RQ Jobs',
304
+ 'description': 'View and manage background jobs',
305
+ 'icon': 'assignment',
306
+ 'link': '/cfg/rq/jobs/',
349
307
  'color': 'secondary',
350
- 'category': 'system',
308
+ 'category': 'monitoring',
351
309
  },
352
310
  {
353
- 'title': 'Clear Cache',
354
- 'description': 'Clear application cache',
355
- 'icon': 'refresh',
356
- 'link': '/cfg/admin/cache/clear/',
357
- 'color': 'warning',
358
- 'category': 'system',
311
+ 'title': 'RQ Schedules',
312
+ 'description': 'Manage scheduled tasks',
313
+ 'icon': 'schedule',
314
+ 'link': '/cfg/rq/schedules/',
315
+ 'color': 'info',
316
+ 'category': 'monitoring',
359
317
  },
360
318
  {
361
- 'title': 'Run Backup',
362
- 'description': 'Create system backup',
363
- 'icon': 'backup',
364
- 'link': '/cfg/admin/backup/create/',
365
- 'color': 'success',
366
- 'category': 'system',
319
+ 'title': 'Commands',
320
+ 'description': 'Execute Django management commands',
321
+ 'icon': 'terminal',
322
+ 'link': '/cfg/dashboard/api/commands/',
323
+ 'color': 'warning',
324
+ 'category': 'admin',
367
325
  },
368
326
  ]
369
327
 
@@ -21,7 +21,6 @@ from .views import (
21
21
  ChartsViewSet,
22
22
  CommandsViewSet,
23
23
  APIZonesViewSet,
24
- DjangoQ2ViewSet,
25
24
  )
26
25
 
27
26
  app_name = 'django_cfg_dashboard'
@@ -35,7 +34,6 @@ router.register(r'activity', ActivityViewSet, basename='activity')
35
34
  router.register(r'charts', ChartsViewSet, basename='charts')
36
35
  router.register(r'commands', CommandsViewSet, basename='commands')
37
36
  router.register(r'zones', APIZonesViewSet, basename='zones')
38
- router.register(r'django_q2', DjangoQ2ViewSet, basename='django_q2')
39
37
 
40
38
  urlpatterns = [
41
39
  # RESTful API endpoints using ViewSets
@@ -11,7 +11,6 @@ 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 .django_q2_views import DjangoQ2ViewSet
15
14
 
16
15
  __all__ = [
17
16
  'OverviewViewSet',
@@ -21,5 +20,4 @@ __all__ = [
21
20
  'ChartsViewSet',
22
21
  'CommandsViewSet',
23
22
  'APIZonesViewSet',
24
- 'DjangoQ2ViewSet',
25
23
  ]
@@ -12,14 +12,12 @@ import json
12
12
  import logging
13
13
 
14
14
  from django.http import StreamingHttpResponse
15
+ from django_cfg.mixins import SuperAdminAPIMixin
15
16
  from drf_spectacular.utils import extend_schema
16
17
  from rest_framework import status, viewsets
17
- from rest_framework.authentication import BasicAuthentication, SessionAuthentication
18
18
  from rest_framework.decorators import action
19
19
  from rest_framework.response import Response
20
- from rest_framework_simplejwt.authentication import JWTAuthentication
21
20
 
22
- from ..permissions import IsSuperAdmin
23
21
  from ..services import CommandsService
24
22
  from ..serializers import (
25
23
  CommandSerializer,
@@ -31,15 +29,14 @@ from ..serializers import (
31
29
  logger = logging.getLogger(__name__)
32
30
 
33
31
 
34
- class CommandsViewSet(viewsets.GenericViewSet):
32
+ class CommandsViewSet(SuperAdminAPIMixin, viewsets.GenericViewSet):
35
33
  """
36
34
  Commands ViewSet
37
35
 
38
36
  Provides endpoints for Django management commands discovery.
37
+ Requires superuser privileges for all operations.
39
38
  """
40
39
 
41
- authentication_classes = [JWTAuthentication, SessionAuthentication, BasicAuthentication]
42
- permission_classes = [IsSuperAdmin] # Only superusers can access commands
43
40
  serializer_class = CommandSerializer
44
41
  pagination_class = None # Disable pagination for commands list
45
42
 
@@ -8,6 +8,7 @@ Endpoint for complete dashboard overview:
8
8
  import logging
9
9
  from datetime import datetime
10
10
 
11
+ from django.db import transaction
11
12
  from drf_spectacular.utils import extend_schema
12
13
  from rest_framework import status, viewsets
13
14
 
@@ -32,6 +33,11 @@ class OverviewViewSet(AdminAPIMixin, viewsets.GenericViewSet):
32
33
 
33
34
  serializer_class = DashboardOverviewSerializer
34
35
 
36
+ @transaction.non_atomic_requests
37
+ def dispatch(self, request, *args, **kwargs):
38
+ """Disable atomic requests for this viewset."""
39
+ return super().dispatch(request, *args, **kwargs)
40
+
35
41
  @extend_schema(
36
42
  summary="Get dashboard overview",
37
43
  description="Retrieve complete dashboard data including stats, health, actions, and metrics",
@@ -40,24 +46,19 @@ class OverviewViewSet(AdminAPIMixin, viewsets.GenericViewSet):
40
46
  )
41
47
  @action(detail=False, methods=['get'], url_path='', url_name='overview')
42
48
  def overview(self, request):
43
- """
44
- Get complete dashboard overview.
45
-
46
- Returns all dashboard data in a single request:
47
- - Statistics cards
48
- - System health status
49
- - Quick actions
50
- - Recent activity
51
- - System metrics
52
- - User statistics
53
- """
49
+ """Get complete dashboard overview."""
54
50
  try:
55
51
  stats_service = StatisticsService()
56
52
  health_service = SystemHealthService()
57
53
  charts_service = ChartsService()
58
54
 
59
- # Get app statistics and convert to list format
60
- app_stats_dict = stats_service.get_app_statistics()
55
+ # Get app statistics - wrapped in try/except since it queries all models
56
+ try:
57
+ app_stats_dict = stats_service.get_app_statistics()
58
+ except Exception as e:
59
+ logger.error(f"Error getting app stats: {e}")
60
+ app_stats_dict = {'apps': {}}
61
+
61
62
  app_statistics_list = [
62
63
  {
63
64
  'app_name': app_label,
@@ -21,8 +21,8 @@ class KnowbaseConfig(AppConfig):
21
21
  # Connect post-migrate signal for database setup
22
22
  post_migrate.connect(self.create_pgvector_extension, sender=self)
23
23
 
24
- # Note: Task system initialization removed - ReArq doesn't need it
25
- # Tasks are auto-discovered from decorated functions
24
+ # Note: Task system initialization removed - tasks are handled by Django-RQ
25
+ # Background tasks can be scheduled using django_rq.enqueue()
26
26
 
27
27
  def create_pgvector_extension(self, sender, **kwargs):
28
28
  """Create pgvector extension and indexes if they don't exist."""
@@ -184,10 +184,9 @@ class CloudflareApiKeyAdmin(PydanticAdmin):
184
184
  ]
185
185
 
186
186
  total_count = obj.cloudflaresite_set.count()
187
- if total_count > 10:
188
- site_items.append(f"... and {total_count - 10} more sites")
187
+ overflow_item = [f"... and {total_count - 10} more sites"] if total_count > 10 else []
189
188
 
190
- return "\n".join(site_items)
189
+ return "\n".join(site_items + overflow_item)
191
190
  sites_using_key.short_description = "Sites Using This Key"
192
191
 
193
192
  def changelist_view(self, request, extra_context=None):
@@ -243,19 +243,20 @@ class EmailLogAdmin(PydanticAdmin):
243
243
  @computed_field("Tracking")
244
244
  def tracking_display(self, obj: EmailLog) -> str:
245
245
  """Display tracking status with badges."""
246
- badges = []
247
-
248
- if obj.is_opened:
249
- badges.append(self.html.badge("Opened", variant="success", icon=Icons.VISIBILITY))
250
- else:
251
- badges.append(self.html.badge("Not Opened", variant="secondary", icon=Icons.VISIBILITY_OFF))
246
+ # Declarative approach - no imperative .append()
247
+ opened_badge = (
248
+ self.html.badge("Opened", variant="success", icon=Icons.VISIBILITY)
249
+ if obj.is_opened else
250
+ self.html.badge("Not Opened", variant="secondary", icon=Icons.VISIBILITY_OFF)
251
+ )
252
252
 
253
- if obj.is_clicked:
254
- badges.append(self.html.badge("Clicked", variant="info", icon=Icons.MOUSE))
255
- else:
256
- badges.append(self.html.badge("Not Clicked", variant="secondary", icon=Icons.TOUCH_APP))
253
+ clicked_badge = (
254
+ self.html.badge("Clicked", variant="info", icon=Icons.MOUSE)
255
+ if obj.is_clicked else
256
+ self.html.badge("Not Clicked", variant="secondary", icon=Icons.TOUCH_APP)
257
+ )
257
258
 
258
- return " | ".join(badges)
259
+ return self.html.inline(opened_badge, clicked_badge, separator=" | ")
259
260
 
260
261
 
261
262
  # ===== Newsletter Admin Config =====
@@ -0,0 +1,9 @@
1
+ """
2
+ Django-RQ integration for django-cfg.
3
+
4
+ Provides REST API endpoints for monitoring and managing RQ task queues.
5
+ """
6
+
7
+ default_app_config = 'django_cfg.apps.rq.apps.RQAppConfig'
8
+
9
+ __version__ = '1.0.0'