django-cfg 1.4.120__py3-none-any.whl → 1.5.2__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 (182) hide show
  1. django_cfg/__init__.py +8 -4
  2. django_cfg/apps/centrifugo/admin/centrifugo_log.py +33 -71
  3. django_cfg/apps/dashboard/TRANSACTION_FIX.md +73 -0
  4. django_cfg/apps/dashboard/serializers/__init__.py +0 -12
  5. django_cfg/apps/dashboard/serializers/activity.py +1 -1
  6. django_cfg/apps/dashboard/services/__init__.py +0 -2
  7. django_cfg/apps/dashboard/services/charts_service.py +4 -3
  8. django_cfg/apps/dashboard/services/statistics_service.py +11 -2
  9. django_cfg/apps/dashboard/services/system_health_service.py +64 -106
  10. django_cfg/apps/dashboard/urls.py +0 -2
  11. django_cfg/apps/dashboard/views/__init__.py +0 -2
  12. django_cfg/apps/dashboard/views/commands_views.py +3 -6
  13. django_cfg/apps/dashboard/views/overview_views.py +14 -13
  14. django_cfg/apps/grpc/__init__.py +9 -0
  15. django_cfg/apps/grpc/admin/__init__.py +11 -0
  16. django_cfg/apps/{tasks → grpc}/admin/config.py +32 -41
  17. django_cfg/apps/grpc/admin/grpc_request_log.py +252 -0
  18. django_cfg/apps/grpc/apps.py +28 -0
  19. django_cfg/apps/grpc/auth/__init__.py +9 -0
  20. django_cfg/apps/grpc/auth/jwt_auth.py +295 -0
  21. django_cfg/apps/grpc/interceptors/__init__.py +19 -0
  22. django_cfg/apps/grpc/interceptors/errors.py +241 -0
  23. django_cfg/apps/grpc/interceptors/logging.py +270 -0
  24. django_cfg/apps/grpc/interceptors/metrics.py +306 -0
  25. django_cfg/apps/grpc/interceptors/request_logger.py +515 -0
  26. django_cfg/apps/grpc/management/__init__.py +1 -0
  27. django_cfg/apps/grpc/management/commands/rungrpc.py +302 -0
  28. django_cfg/apps/grpc/managers/__init__.py +10 -0
  29. django_cfg/apps/grpc/managers/grpc_request_log.py +310 -0
  30. django_cfg/apps/grpc/migrations/0001_initial.py +69 -0
  31. django_cfg/apps/grpc/migrations/0002_rename_django_cfg__service_4c4a8e_idx_django_cfg__service_584308_idx_and_more.py +38 -0
  32. django_cfg/apps/grpc/models/__init__.py +9 -0
  33. django_cfg/apps/grpc/models/grpc_request_log.py +219 -0
  34. django_cfg/apps/grpc/serializers/__init__.py +23 -0
  35. django_cfg/apps/grpc/serializers/health.py +18 -0
  36. django_cfg/apps/grpc/serializers/requests.py +18 -0
  37. django_cfg/apps/grpc/serializers/services.py +50 -0
  38. django_cfg/apps/grpc/serializers/stats.py +22 -0
  39. django_cfg/apps/grpc/services/__init__.py +16 -0
  40. django_cfg/apps/grpc/services/base.py +375 -0
  41. django_cfg/apps/grpc/services/discovery.py +415 -0
  42. django_cfg/apps/grpc/urls.py +23 -0
  43. django_cfg/apps/grpc/utils/__init__.py +13 -0
  44. django_cfg/apps/grpc/utils/proto_gen.py +423 -0
  45. django_cfg/apps/grpc/views/__init__.py +9 -0
  46. django_cfg/apps/grpc/views/monitoring.py +497 -0
  47. django_cfg/apps/knowbase/apps.py +2 -2
  48. django_cfg/apps/maintenance/admin/api_key_admin.py +7 -9
  49. django_cfg/apps/maintenance/admin/site_admin.py +5 -4
  50. django_cfg/apps/newsletter/admin/newsletter_admin.py +12 -11
  51. django_cfg/apps/payments/admin/balance_admin.py +26 -36
  52. django_cfg/apps/payments/admin/payment_admin.py +65 -85
  53. django_cfg/apps/payments/admin/withdrawal_admin.py +65 -100
  54. django_cfg/apps/rq/__init__.py +9 -0
  55. django_cfg/apps/rq/apps.py +80 -0
  56. django_cfg/apps/rq/management/__init__.py +1 -0
  57. django_cfg/apps/rq/management/commands/__init__.py +1 -0
  58. django_cfg/apps/rq/management/commands/rqscheduler.py +31 -0
  59. django_cfg/apps/rq/management/commands/rqstats.py +33 -0
  60. django_cfg/apps/rq/management/commands/rqworker.py +31 -0
  61. django_cfg/apps/rq/management/commands/rqworker_pool.py +27 -0
  62. django_cfg/apps/rq/serializers/__init__.py +40 -0
  63. django_cfg/apps/rq/serializers/health.py +60 -0
  64. django_cfg/apps/rq/serializers/job.py +100 -0
  65. django_cfg/apps/rq/serializers/queue.py +80 -0
  66. django_cfg/apps/rq/serializers/schedule.py +178 -0
  67. django_cfg/apps/rq/serializers/testing.py +139 -0
  68. django_cfg/apps/rq/serializers/worker.py +58 -0
  69. django_cfg/apps/rq/services/__init__.py +25 -0
  70. django_cfg/apps/rq/services/config_helper.py +233 -0
  71. django_cfg/apps/rq/services/models/README.md +417 -0
  72. django_cfg/apps/rq/services/models/__init__.py +30 -0
  73. django_cfg/apps/rq/services/models/event.py +123 -0
  74. django_cfg/apps/rq/services/models/job.py +99 -0
  75. django_cfg/apps/rq/services/models/queue.py +92 -0
  76. django_cfg/apps/rq/services/models/worker.py +104 -0
  77. django_cfg/apps/rq/services/rq_converters.py +183 -0
  78. django_cfg/apps/rq/tasks/__init__.py +23 -0
  79. django_cfg/apps/rq/tasks/demo_tasks.py +284 -0
  80. django_cfg/apps/rq/urls.py +54 -0
  81. django_cfg/apps/rq/views/__init__.py +19 -0
  82. django_cfg/apps/rq/views/jobs.py +882 -0
  83. django_cfg/apps/rq/views/monitoring.py +248 -0
  84. django_cfg/apps/rq/views/queues.py +261 -0
  85. django_cfg/apps/rq/views/schedule.py +400 -0
  86. django_cfg/apps/rq/views/testing.py +761 -0
  87. django_cfg/apps/rq/views/workers.py +195 -0
  88. django_cfg/apps/urls.py +13 -8
  89. django_cfg/config.py +106 -0
  90. django_cfg/core/base/config_model.py +16 -26
  91. django_cfg/core/builders/apps_builder.py +7 -11
  92. django_cfg/core/generation/integration_generators/__init__.py +3 -6
  93. django_cfg/core/generation/integration_generators/django_rq.py +80 -0
  94. django_cfg/core/generation/integration_generators/grpc_generator.py +318 -0
  95. django_cfg/core/generation/orchestrator.py +15 -15
  96. django_cfg/core/integration/display/startup.py +6 -20
  97. django_cfg/mixins/__init__.py +2 -0
  98. django_cfg/mixins/superadmin_api.py +59 -0
  99. django_cfg/models/__init__.py +3 -3
  100. django_cfg/models/api/grpc/__init__.py +59 -0
  101. django_cfg/models/api/grpc/config.py +364 -0
  102. django_cfg/models/django/__init__.py +3 -3
  103. django_cfg/models/django/django_rq.py +621 -0
  104. django_cfg/models/django/revolution_legacy.py +1 -1
  105. django_cfg/modules/base.py +19 -6
  106. django_cfg/modules/django_admin/base/pydantic_admin.py +2 -2
  107. django_cfg/modules/django_admin/config/background_task_config.py +4 -4
  108. django_cfg/modules/django_admin/utils/__init__.py +41 -3
  109. django_cfg/modules/django_admin/utils/badges/__init__.py +13 -0
  110. django_cfg/modules/django_admin/utils/{badges.py → badges/status_badges.py} +3 -3
  111. django_cfg/modules/django_admin/utils/displays/__init__.py +13 -0
  112. django_cfg/modules/django_admin/utils/{displays.py → displays/data_displays.py} +2 -2
  113. django_cfg/modules/django_admin/utils/html/__init__.py +26 -0
  114. django_cfg/modules/django_admin/utils/html/badges.py +47 -0
  115. django_cfg/modules/django_admin/utils/html/base.py +167 -0
  116. django_cfg/modules/django_admin/utils/html/code.py +87 -0
  117. django_cfg/modules/django_admin/utils/html/composition.py +205 -0
  118. django_cfg/modules/django_admin/utils/html/formatting.py +231 -0
  119. django_cfg/modules/django_admin/utils/html/keyvalue.py +219 -0
  120. django_cfg/modules/django_admin/utils/html/markdown_integration.py +108 -0
  121. django_cfg/modules/django_admin/utils/html/progress.py +127 -0
  122. django_cfg/modules/django_admin/utils/html_builder.py +55 -408
  123. django_cfg/modules/django_admin/utils/markdown/__init__.py +21 -0
  124. django_cfg/modules/django_unfold/navigation.py +21 -18
  125. django_cfg/pyproject.toml +4 -6
  126. django_cfg/registry/core.py +4 -7
  127. django_cfg/registry/modules.py +6 -0
  128. django_cfg/static/frontend/admin.zip +0 -0
  129. django_cfg/templates/admin/constance/includes/results_list.html +73 -0
  130. django_cfg/templates/admin/index.html +187 -62
  131. django_cfg/templatetags/django_cfg.py +61 -1
  132. {django_cfg-1.4.120.dist-info → django_cfg-1.5.2.dist-info}/METADATA +12 -4
  133. {django_cfg-1.4.120.dist-info → django_cfg-1.5.2.dist-info}/RECORD +140 -96
  134. django_cfg/apps/dashboard/permissions.py +0 -48
  135. django_cfg/apps/dashboard/serializers/django_q2.py +0 -50
  136. django_cfg/apps/dashboard/services/django_q2_service.py +0 -159
  137. django_cfg/apps/dashboard/views/django_q2_views.py +0 -79
  138. django_cfg/apps/tasks/__init__.py +0 -64
  139. django_cfg/apps/tasks/admin/__init__.py +0 -4
  140. django_cfg/apps/tasks/admin/task_log.py +0 -265
  141. django_cfg/apps/tasks/apps.py +0 -15
  142. django_cfg/apps/tasks/filters/__init__.py +0 -10
  143. django_cfg/apps/tasks/filters/task_log.py +0 -121
  144. django_cfg/apps/tasks/migrations/0001_initial.py +0 -196
  145. django_cfg/apps/tasks/migrations/0002_delete_tasklog.py +0 -16
  146. django_cfg/apps/tasks/models/__init__.py +0 -4
  147. django_cfg/apps/tasks/models/task_log.py +0 -246
  148. django_cfg/apps/tasks/serializers/__init__.py +0 -28
  149. django_cfg/apps/tasks/serializers/task_log.py +0 -249
  150. django_cfg/apps/tasks/services/__init__.py +0 -10
  151. django_cfg/apps/tasks/services/client/__init__.py +0 -7
  152. django_cfg/apps/tasks/services/client/client.py +0 -234
  153. django_cfg/apps/tasks/services/config_helper.py +0 -63
  154. django_cfg/apps/tasks/services/sync.py +0 -204
  155. django_cfg/apps/tasks/urls.py +0 -16
  156. django_cfg/apps/tasks/views/__init__.py +0 -10
  157. django_cfg/apps/tasks/views/task_log.py +0 -41
  158. django_cfg/apps/tasks/views/task_log_base.py +0 -41
  159. django_cfg/apps/tasks/views/task_log_overview.py +0 -100
  160. django_cfg/apps/tasks/views/task_log_related.py +0 -41
  161. django_cfg/apps/tasks/views/task_log_stats.py +0 -91
  162. django_cfg/apps/tasks/views/task_log_timeline.py +0 -81
  163. django_cfg/core/generation/integration_generators/django_q2.py +0 -133
  164. django_cfg/core/generation/integration_generators/tasks.py +0 -88
  165. django_cfg/models/django/django_q2.py +0 -514
  166. django_cfg/models/tasks/__init__.py +0 -49
  167. django_cfg/models/tasks/backends.py +0 -122
  168. django_cfg/models/tasks/config.py +0 -209
  169. django_cfg/models/tasks/utils.py +0 -162
  170. django_cfg/modules/django_admin/utils/CODE_BLOCK_DOCS.md +0 -396
  171. django_cfg/modules/django_q2/README.md +0 -140
  172. django_cfg/modules/django_q2/__init__.py +0 -8
  173. django_cfg/modules/django_q2/apps.py +0 -107
  174. django_cfg/modules/django_q2/management/commands/__init__.py +0 -0
  175. django_cfg/modules/django_q2/management/commands/sync_django_q_schedules.py +0 -74
  176. /django_cfg/apps/{tasks/migrations → grpc/management/commands}/__init__.py +0 -0
  177. /django_cfg/{modules/django_q2/management → apps/grpc/migrations}/__init__.py +0 -0
  178. /django_cfg/modules/django_admin/utils/{mermaid_plugin.py → markdown/mermaid_plugin.py} +0 -0
  179. /django_cfg/modules/django_admin/utils/{markdown_renderer.py → markdown/renderer.py} +0 -0
  180. {django_cfg-1.4.120.dist-info → django_cfg-1.5.2.dist-info}/WHEEL +0 -0
  181. {django_cfg-1.4.120.dist-info → django_cfg-1.5.2.dist-info}/entry_points.txt +0 -0
  182. {django_cfg-1.4.120.dist-info → django_cfg-1.5.2.dist-info}/licenses/LICENSE +0 -0
django_cfg/__init__.py CHANGED
@@ -32,11 +32,11 @@ Example:
32
32
  default_app_config = "django_cfg.apps.DjangoCfgConfig"
33
33
 
34
34
  # Version information
35
- __version__ = "1.4.120"
35
+ __version__ = "1.5.2"
36
36
  __license__ = "MIT"
37
37
 
38
38
  # Import registry for organized lazy loading
39
- from .config import LIB_NAME
39
+ from .config import LIB_NAME, is_feature_available, require_feature, register_feature
40
40
  from .registry import DJANGO_CFG_REGISTRY
41
41
 
42
42
  # Get author from library config
@@ -55,5 +55,9 @@ def __getattr__(name: str):
55
55
  raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
56
56
 
57
57
 
58
- # Export all registered components
59
- __all__ = list(DJANGO_CFG_REGISTRY.keys())
58
+ # Export all registered components + feature detection
59
+ __all__ = list(DJANGO_CFG_REGISTRY.keys()) + [
60
+ "is_feature_available",
61
+ "require_feature",
62
+ "register_feature",
63
+ ]
@@ -96,7 +96,7 @@ class CentrifugoLogAdmin(PydanticAdmin):
96
96
 
97
97
  try:
98
98
  formatted = json.dumps(obj.data, indent=2)
99
- return f'<pre style="background: #f5f5f5; padding: 10px; border-radius: 4px; max-height: 400px; overflow: auto; font-size: 12px; line-height: 1.5;">{formatted}</pre>'
99
+ return self.html.code_block(formatted, language="json", max_height="400px")
100
100
  except Exception:
101
101
  return str(obj.data)
102
102
 
@@ -106,37 +106,22 @@ class CentrifugoLogAdmin(PydanticAdmin):
106
106
  """Display error information if publish failed."""
107
107
  if obj.is_successful or obj.status == "pending":
108
108
  return self.html.inline(
109
- [
110
- self.html.icon(Icons.CHECK_CIRCLE, size="sm"),
111
- self.html.span("No errors", "text-green-600"),
112
- ]
109
+ self.html.icon(Icons.CHECK_CIRCLE, size="sm"),
110
+ self.html.text("No errors", variant="success"),
111
+ separator=" "
113
112
  )
114
113
 
115
- details = []
116
-
117
- if obj.error_code:
118
- details.append(
119
- self.html.inline(
120
- [
121
- self.html.span("Error Code:", "font-semibold"),
122
- self.html.badge(obj.error_code, variant="danger", icon=Icons.ERROR),
123
- ],
124
- separator=" ",
125
- )
126
- )
114
+ error_code_line = self.html.key_value(
115
+ "Error Code",
116
+ self.html.badge(obj.error_code, variant="danger", icon=Icons.ERROR)
117
+ ) if obj.error_code else None
127
118
 
128
- if obj.error_message:
129
- details.append(
130
- self.html.inline(
131
- [
132
- self.html.span("Message:", "font-semibold"),
133
- self.html.span(obj.error_message, "text-red-600"),
134
- ],
135
- separator=" ",
136
- )
137
- )
119
+ error_msg_line = self.html.key_value(
120
+ "Message",
121
+ self.html.text(obj.error_message, variant="danger")
122
+ ) if obj.error_message else None
138
123
 
139
- return "<br>".join(details) if details else self.html.empty()
124
+ return self.html.breakdown(error_code_line, error_msg_line) if (error_code_line or error_msg_line) else self.html.empty()
140
125
 
141
126
  error_details_display.short_description = "Error Details"
142
127
 
@@ -145,57 +130,34 @@ class CentrifugoLogAdmin(PydanticAdmin):
145
130
  if not obj.wait_for_ack:
146
131
  return self.html.empty("No ACK tracking")
147
132
 
148
- stats = []
149
-
150
133
  # ACK timeout
151
- if obj.ack_timeout:
152
- stats.append(
153
- self.html.inline(
154
- [
155
- self.html.span("Timeout:", "font-semibold"),
156
- self.html.span(f"{obj.ack_timeout}s", "text-gray-600"),
157
- ],
158
- separator=" ",
159
- )
160
- )
134
+ timeout_line = self.html.key_value(
135
+ "Timeout",
136
+ f"{obj.ack_timeout}s"
137
+ ) if obj.ack_timeout else None
161
138
 
162
139
  # ACKs received
163
- stats.append(
164
- self.html.inline(
165
- [
166
- self.html.span("ACKs Received:", "font-semibold"),
167
- self.html.badge(str(obj.acks_received), variant="info"),
168
- ],
169
- separator=" ",
170
- )
140
+ received_line = self.html.key_value(
141
+ "ACKs Received",
142
+ self.html.badge(str(obj.acks_received), variant="info")
171
143
  )
172
144
 
173
145
  # ACKs expected (if known)
174
- if obj.acks_expected:
175
- stats.append(
176
- self.html.inline(
177
- [
178
- self.html.span("ACKs Expected:", "font-semibold"),
179
- self.html.span(str(obj.acks_expected), "text-gray-600"),
180
- ],
181
- separator=" ",
182
- )
146
+ expected_line = self.html.key_value(
147
+ "ACKs Expected",
148
+ str(obj.acks_expected)
149
+ ) if obj.acks_expected else None
150
+
151
+ # Delivery rate
152
+ rate_line = None
153
+ if obj.acks_expected and obj.delivery_rate is not None:
154
+ rate_pct = obj.delivery_rate * 100
155
+ rate_line = self.html.key_value(
156
+ "Delivery Rate",
157
+ self.html.number(rate_pct, precision=1, suffix="%")
183
158
  )
184
159
 
185
- # Delivery rate
186
- if obj.delivery_rate is not None:
187
- rate_pct = obj.delivery_rate * 100
188
- stats.append(
189
- self.html.inline(
190
- [
191
- self.html.span("Delivery Rate:", "font-semibold"),
192
- self.html.span(f"{rate_pct:.1f}%", "text-blue-600"),
193
- ],
194
- separator=" ",
195
- )
196
- )
197
-
198
- return "<br>".join(stats) if stats else self.html.empty()
160
+ return self.html.breakdown(timeout_line, received_line, expected_line, rate_line)
199
161
 
200
162
  delivery_stats_display.short_description = "Delivery Statistics"
201
163
 
@@ -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