django-cfg 1.4.88__py3-none-any.whl → 1.4.89__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 (146) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/centrifugo/views/__init__.py +0 -2
  3. django_cfg/apps/dashboard/services/__init__.py +2 -0
  4. django_cfg/apps/dashboard/services/overview_service.py +205 -0
  5. django_cfg/apps/frontend/test_routing.py +134 -0
  6. django_cfg/apps/frontend/views.py +69 -28
  7. django_cfg/apps/urls.py +0 -1
  8. django_cfg/core/builders/apps_builder.py +0 -58
  9. django_cfg/modules/django_unfold/__init__.py +5 -24
  10. django_cfg/modules/django_unfold/models/__init__.py +0 -23
  11. django_cfg/modules/django_unfold/models/config.py +11 -65
  12. django_cfg/modules/django_unfold/{dashboard.py → navigation.py} +21 -152
  13. django_cfg/modules/django_unfold/tailwind.py +2 -4
  14. django_cfg/pyproject.toml +1 -1
  15. django_cfg/registry/third_party.py +0 -9
  16. django_cfg/routing/callbacks.py +1 -43
  17. django_cfg/static/frontend/admin/404/index.html +1 -1
  18. django_cfg/static/frontend/admin/404.html +1 -1
  19. django_cfg/static/frontend/admin/500/index.html +1 -1
  20. django_cfg/static/frontend/admin/_next/static/{D_d9HRw5Yn7BRHAX5q89_ → 0sN9ktsgXH48ygtGSrhfu}/_buildManifest.js +1 -1
  21. django_cfg/static/frontend/admin/_next/static/chunks/50314-9443faa6df24aebf.js +1 -0
  22. django_cfg/static/frontend/admin/_next/static/chunks/pages/{_app-1c0fff0f59a6d683.js → _app-c7dcd3aa616fab68.js} +1 -1
  23. django_cfg/static/frontend/admin/_next/static/chunks/pages/private/{centrifugo-44a8313fa040e9ad.js → centrifugo-f9ecbc3ae0052a03.js} +1 -1
  24. django_cfg/static/frontend/admin/auth/index.html +1 -1
  25. django_cfg/static/frontend/admin/index.html +1 -1
  26. django_cfg/static/frontend/admin/legal/cookies/index.html +1 -1
  27. django_cfg/static/frontend/admin/legal/privacy/index.html +1 -1
  28. django_cfg/static/frontend/admin/legal/security/index.html +1 -1
  29. django_cfg/static/frontend/admin/legal/terms/index.html +1 -1
  30. django_cfg/static/frontend/admin/private/centrifugo/index.html +1 -1
  31. django_cfg/static/frontend/admin/private/index.html +1 -1
  32. django_cfg/static/frontend/admin/private/profile/index.html +1 -1
  33. django_cfg/static/frontend/admin/private/ui/index.html +2 -2
  34. {django_cfg-1.4.88.dist-info → django_cfg-1.4.89.dist-info}/METADATA +1 -1
  35. {django_cfg-1.4.88.dist-info → django_cfg-1.4.89.dist-info}/RECORD +39 -143
  36. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/css/dashboard.css +0 -260
  37. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/live_channels.mjs +0 -313
  38. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/live_testing.mjs +0 -803
  39. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/main.mjs +0 -341
  40. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/overview.mjs +0 -432
  41. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/testing.mjs +0 -33
  42. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/websocket.mjs +0 -210
  43. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/channels_content.html +0 -46
  44. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/live_channels_content.html +0 -123
  45. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/overview_content.html +0 -45
  46. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/publishes_content.html +0 -84
  47. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/stat_cards.html +0 -53
  48. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/system_status.html +0 -91
  49. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/tab_navigation.html +0 -29
  50. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/testing_tools.html +0 -415
  51. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/layout/base.html +0 -61
  52. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/pages/dashboard.html +0 -58
  53. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/tags/connection_script.html +0 -48
  54. django_cfg/apps/centrifugo/templatetags/__init__.py +0 -1
  55. django_cfg/apps/centrifugo/templatetags/centrifugo_tags.py +0 -81
  56. django_cfg/apps/centrifugo/urls_admin.py +0 -20
  57. django_cfg/apps/centrifugo/views/dashboard.py +0 -28
  58. django_cfg/modules/django_dashboard/__init__.py +0 -23
  59. django_cfg/modules/django_dashboard/components.py +0 -312
  60. django_cfg/modules/django_dashboard/debug.py +0 -174
  61. django_cfg/modules/django_dashboard/management/__init__.py +0 -0
  62. django_cfg/modules/django_dashboard/management/commands/__init__.py +0 -0
  63. django_cfg/modules/django_dashboard/management/commands/debug_dashboard.py +0 -109
  64. django_cfg/modules/django_dashboard/sections/__init__.py +0 -1
  65. django_cfg/modules/django_dashboard/sections/base.py +0 -129
  66. django_cfg/modules/django_dashboard/sections/commands.py +0 -33
  67. django_cfg/modules/django_dashboard/sections/documentation.py +0 -393
  68. django_cfg/modules/django_dashboard/sections/overview.py +0 -398
  69. django_cfg/modules/django_dashboard/sections/stats.py +0 -48
  70. django_cfg/modules/django_dashboard/sections/system.py +0 -74
  71. django_cfg/modules/django_dashboard/sections/widgets.py +0 -222
  72. django_cfg/modules/django_unfold/callbacks/__init__.py +0 -9
  73. django_cfg/modules/django_unfold/callbacks/actions.py +0 -51
  74. django_cfg/modules/django_unfold/callbacks/apizones.py +0 -122
  75. django_cfg/modules/django_unfold/callbacks/base.py +0 -290
  76. django_cfg/modules/django_unfold/callbacks/charts.py +0 -223
  77. django_cfg/modules/django_unfold/callbacks/commands.py +0 -40
  78. django_cfg/modules/django_unfold/callbacks/main.py +0 -322
  79. django_cfg/modules/django_unfold/callbacks/statistics.py +0 -240
  80. django_cfg/modules/django_unfold/callbacks/system.py +0 -180
  81. django_cfg/modules/django_unfold/callbacks/users.py +0 -65
  82. django_cfg/modules/django_unfold/models/dashboard.py +0 -207
  83. django_cfg/modules/django_unfold/models/tabs.py +0 -26
  84. django_cfg/modules/django_unfold/models.py +0 -98
  85. django_cfg/modules/django_unfold/templates/unfold/helpers/app_list.html +0 -102
  86. django_cfg/static/frontend/admin/_next/static/chunks/50314-5ec79b293c2283dd.js +0 -1
  87. django_cfg/templates/admin/sections/commands_section.html +0 -5
  88. django_cfg/templates/admin/sections/documentation_section.html +0 -5
  89. django_cfg/templates/admin/sections/overview_section.html +0 -5
  90. django_cfg/templates/admin/sections/stats_section.html +0 -5
  91. django_cfg/templates/admin/sections/system_section.html +0 -5
  92. django_cfg/templates/admin/sections/widgets_section.html +0 -11
  93. django_cfg/templates/admin_old/components/action_grid.html +0 -49
  94. django_cfg/templates/admin_old/components/card.html +0 -50
  95. django_cfg/templates/admin_old/components/data_table.html +0 -67
  96. django_cfg/templates/admin_old/components/metric_card.html +0 -39
  97. django_cfg/templates/admin_old/components/modal.html +0 -58
  98. django_cfg/templates/admin_old/components/progress_bar.html +0 -20
  99. django_cfg/templates/admin_old/components/section_header.html +0 -26
  100. django_cfg/templates/admin_old/components/stat_item.html +0 -32
  101. django_cfg/templates/admin_old/components/stats_grid.html +0 -72
  102. django_cfg/templates/admin_old/components/status_badge.html +0 -28
  103. django_cfg/templates/admin_old/components/user_avatar.html +0 -27
  104. django_cfg/templates/admin_old/constance/change_list.html +0 -74
  105. django_cfg/templates/admin_old/constance/includes/default_value.html +0 -24
  106. django_cfg/templates/admin_old/constance/includes/fieldset_header.html +0 -15
  107. django_cfg/templates/admin_old/constance/includes/results_list.html +0 -16
  108. django_cfg/templates/admin_old/constance/includes/setting_row.html +0 -50
  109. django_cfg/templates/admin_old/constance/includes/table_headers.html +0 -10
  110. django_cfg/templates/admin_old/examples/component_class_example.html +0 -156
  111. django_cfg/templates/admin_old/import_export/change_list_export.html +0 -24
  112. django_cfg/templates/admin_old/import_export/change_list_import.html +0 -24
  113. django_cfg/templates/admin_old/import_export/change_list_import_export.html +0 -34
  114. django_cfg/templates/admin_old/index.html +0 -80
  115. django_cfg/templates/admin_old/index_new.html +0 -119
  116. django_cfg/templates/admin_old/layouts/base_dashboard.html +0 -62
  117. django_cfg/templates/admin_old/layouts/dashboard_with_tabs.html +0 -176
  118. django_cfg/templates/admin_old/sections/commands_section.html +0 -549
  119. django_cfg/templates/admin_old/sections/documentation_section.html +0 -152
  120. django_cfg/templates/admin_old/sections/overview_section.html +0 -112
  121. django_cfg/templates/admin_old/sections/stats_section.html +0 -35
  122. django_cfg/templates/admin_old/sections/system_section.html +0 -99
  123. django_cfg/templates/admin_old/sections/widgets_section.html +0 -129
  124. django_cfg/templates/admin_old/snippets/components/activity_tracker.html +0 -70
  125. django_cfg/templates/admin_old/snippets/components/charts_section.html +0 -113
  126. django_cfg/templates/admin_old/snippets/components/django_commands.html +0 -270
  127. django_cfg/templates/admin_old/snippets/components/quick_actions.html +0 -66
  128. django_cfg/templates/admin_old/snippets/components/recent_activity_improved.html +0 -25
  129. django_cfg/templates/admin_old/snippets/components/recent_users_table.html +0 -102
  130. django_cfg/templates/admin_old/snippets/components/stats_cards.html +0 -4
  131. django_cfg/templates/admin_old/snippets/components/stats_tiles.html +0 -92
  132. django_cfg/templates/admin_old/snippets/components/system_health.html +0 -22
  133. django_cfg/templates/admin_old/snippets/components/system_metrics.html +0 -199
  134. django_cfg/templates/admin_old/snippets/components/user_permissions.html +0 -57
  135. django_cfg/templates/admin_old/snippets/tabs/app_stats_tab.html +0 -201
  136. django_cfg/templates/admin_old/snippets/tabs/commands_tab.html +0 -114
  137. django_cfg/templates/admin_old/snippets/tabs/documentation_tab.html +0 -42
  138. django_cfg/templates/admin_old/snippets/tabs/overview_tab.html +0 -116
  139. django_cfg/templates/admin_old/snippets/tabs/stats_tab.html +0 -89
  140. django_cfg/templates/admin_old/snippets/tabs/users_tab.html +0 -51
  141. django_cfg/templates/admin_old/snippets/tabs/widgets_tab.html +0 -38
  142. django_cfg/templates/admin_old/snippets/zones/zones_table.html +0 -176
  143. /django_cfg/static/frontend/admin/_next/static/{D_d9HRw5Yn7BRHAX5q89_ → 0sN9ktsgXH48ygtGSrhfu}/_ssgManifest.js +0 -0
  144. {django_cfg-1.4.88.dist-info → django_cfg-1.4.89.dist-info}/WHEEL +0 -0
  145. {django_cfg-1.4.88.dist-info → django_cfg-1.4.89.dist-info}/entry_points.txt +0 -0
  146. {django_cfg-1.4.88.dist-info → django_cfg-1.4.89.dist-info}/licenses/LICENSE +0 -0
@@ -1,81 +0,0 @@
1
- """
2
- Centrifugo Template Tags.
3
-
4
- Provides template tags for accessing Centrifugo configuration in templates.
5
- """
6
-
7
- from django import template
8
- from ..services import get_centrifugo_config
9
-
10
- register = template.Library()
11
-
12
-
13
- @register.simple_tag
14
- def centrifugo_admin_url():
15
- """
16
- Get Centrifugo admin UI URL from configuration.
17
-
18
- Returns the HTTP admin URL from centrifugo_api_url.
19
- Example: http://localhost:8002
20
-
21
- Usage in template:
22
- {% load centrifugo_tags %}
23
- {% centrifugo_admin_url %}
24
- """
25
- config = get_centrifugo_config()
26
- if not config or not config.centrifugo_api_url:
27
- return ""
28
-
29
- # Return HTTP API URL (admin UI is on same host)
30
- url = config.centrifugo_api_url
31
-
32
- # Remove /api suffix if present
33
- if url.endswith('/api'):
34
- url = url[:-len('/api')]
35
-
36
- # Remove trailing slash
37
- url = url.rstrip('/')
38
-
39
- return url
40
-
41
-
42
- @register.simple_tag
43
- def centrifugo_is_configured():
44
- """
45
- Check if Centrifugo is configured.
46
-
47
- Returns True if Centrifugo config exists and has URL.
48
-
49
- Usage in template:
50
- {% load centrifugo_tags %}
51
- {% centrifugo_is_configured as is_configured %}
52
- {% if is_configured %}...{% endif %}
53
- """
54
- config = get_centrifugo_config()
55
- return bool(config and config.centrifugo_url)
56
-
57
-
58
- @register.simple_tag
59
- def centrifugo_wrapper_url():
60
- """
61
- Get Centrifugo wrapper URL from configuration.
62
-
63
- Returns the wrapper URL (our Django proxy).
64
- Example: http://localhost:8080
65
-
66
- Usage in template:
67
- {% load centrifugo_tags %}
68
- {% centrifugo_wrapper_url %}
69
- """
70
- config = get_centrifugo_config()
71
- if not config or not config.wrapper_url:
72
- return ""
73
-
74
- return config.wrapper_url.rstrip('/')
75
-
76
-
77
- __all__ = [
78
- "centrifugo_admin_url",
79
- "centrifugo_is_configured",
80
- "centrifugo_wrapper_url",
81
- ]
@@ -1,20 +0,0 @@
1
- """
2
- Admin URLs for Centrifugo dashboard.
3
-
4
- Dashboard interface for monitoring Centrifugo publish activity.
5
- """
6
-
7
- from django.urls import include, path
8
-
9
- from .views import dashboard_view
10
-
11
- app_name = 'django_cfg_centrifugo_admin'
12
-
13
-
14
- urlpatterns = [
15
- # Dashboard page
16
- path('', dashboard_view, name='dashboard'),
17
-
18
- # Include API endpoints for dashboard AJAX calls
19
- path('api/', include('django_cfg.apps.centrifugo.urls')),
20
- ]
@@ -1,28 +0,0 @@
1
- """
2
- Dashboard view for Centrifugo monitoring.
3
- """
4
-
5
- from django.contrib.admin.views.decorators import staff_member_required
6
- from django.shortcuts import render
7
- from django.urls import reverse
8
-
9
-
10
- @staff_member_required
11
- def dashboard_view(request):
12
- """Render the Centrifugo dashboard template."""
13
-
14
- # Navigation items for the navbar
15
- nav_items = [
16
- {
17
- 'label': 'Logs',
18
- 'url': reverse('admin:django_cfg_centrifugo_centrifugolog_changelist'),
19
- 'icon': 'list_alt',
20
- 'active': False,
21
- },
22
- ]
23
-
24
- context = {
25
- 'page_title': 'Centrifugo Monitor Dashboard',
26
- 'centrifugo_nav_items': nav_items,
27
- }
28
- return render(request, 'django_cfg_centrifugo/pages/dashboard.html', context)
@@ -1,23 +0,0 @@
1
- """
2
- Django CFG Dashboard Module.
3
-
4
- Section-based architecture for dashboard rendering.
5
- Inspired by Unfold's clean component approach.
6
- """
7
-
8
- # Import components to register them with Unfold
9
- # The @register_component decorator runs on import
10
- from . import components # noqa: F401
11
- from .sections.base import DashboardSection
12
- from .sections.commands import CommandsSection
13
- from .sections.overview import OverviewSection
14
- from .sections.stats import StatsSection
15
- from .sections.system import SystemSection
16
-
17
- __all__ = [
18
- 'DashboardSection',
19
- 'OverviewSection',
20
- 'StatsSection',
21
- 'SystemSection',
22
- 'CommandsSection',
23
- ]
@@ -1,312 +0,0 @@
1
- """
2
- Dashboard Component Classes
3
-
4
- Uses Unfold's Component Class system for data preprocessing.
5
- Separates business logic from presentation templates.
6
- """
7
-
8
- from typing import Any, Dict
9
-
10
- from unfold.components import BaseComponent, register_component
11
-
12
-
13
- @register_component
14
- class SystemMetricsComponent(BaseComponent):
15
- """
16
- System metrics component for dashboard.
17
-
18
- Provides health metrics for database, cache, storage, and API.
19
- """
20
-
21
- def get_context_data(self, **kwargs):
22
- """Prepare system metrics data."""
23
- context = super().get_context_data(**kwargs)
24
- context.update({
25
- "data": self.get_system_metrics()
26
- })
27
- return context
28
-
29
- def get_system_metrics(self) -> Dict[str, Any]:
30
- """Fetch system health metrics."""
31
- import shutil
32
-
33
- from django.core.cache import cache
34
- from django.db import connection
35
-
36
- metrics = {}
37
-
38
- # Database Health
39
- try:
40
- with connection.cursor() as cursor:
41
- cursor.execute("SELECT 1")
42
- metrics["database"] = {
43
- "value": 95,
44
- "title": "Database Health",
45
- "description": "Connection successful",
46
- }
47
- except Exception as e:
48
- metrics["database"] = {
49
- "value": 0,
50
- "title": "Database Health",
51
- "description": f"Error: {str(e)[:50]}",
52
- }
53
-
54
- # Cache Performance
55
- try:
56
- cache.set("health_check", "ok", 10)
57
- if cache.get("health_check") == "ok":
58
- metrics["cache"] = {
59
- "value": 90,
60
- "title": "Cache Performance",
61
- "description": "Cache working properly",
62
- }
63
- else:
64
- metrics["cache"] = {
65
- "value": 50,
66
- "title": "Cache Performance",
67
- "description": "Cache response delayed",
68
- }
69
- except Exception as e:
70
- metrics["cache"] = {
71
- "value": 0,
72
- "title": "Cache Performance",
73
- "description": f"Error: {str(e)[:50]}",
74
- }
75
-
76
- # Storage Space
77
- try:
78
- total, used, free = shutil.disk_usage("/")
79
- free_percentage = int((free / total) * 100)
80
- usage_percentage = 100 - free_percentage
81
-
82
- metrics["storage"] = {
83
- "value": free_percentage,
84
- "title": "Disk Space",
85
- "description": f"{free_percentage}% free ({usage_percentage}% used)",
86
- }
87
- except Exception as e:
88
- metrics["storage"] = {
89
- "value": 0,
90
- "title": "Disk Space",
91
- "description": f"Error: {str(e)[:50]}",
92
- }
93
-
94
- # API Health
95
- try:
96
- from django.urls import get_resolver
97
- resolver = get_resolver()
98
- url_patterns = list(resolver.url_patterns)
99
-
100
- metrics["api"] = {
101
- "value": 100,
102
- "title": "REST API",
103
- "description": f"{len(url_patterns)} URL patterns",
104
- }
105
- except Exception:
106
- metrics["api"] = {
107
- "value": 50,
108
- "title": "REST API",
109
- "description": "Unable to count URLs",
110
- }
111
-
112
- return metrics
113
-
114
-
115
- @register_component
116
- class RecentUsersComponent(BaseComponent):
117
- """
118
- Recent users table component.
119
-
120
- Provides formatted table data for displaying recent user registrations.
121
- """
122
-
123
- def get_context_data(self, **kwargs):
124
- """Prepare recent users table data."""
125
- context = super().get_context_data(**kwargs)
126
-
127
- from django.contrib.auth import get_user_model
128
- User = get_user_model()
129
-
130
- try:
131
- recent_users = User.objects.order_by('-date_joined')[:10]
132
-
133
- context.update({
134
- "data": {
135
- "headers": ["Username", "Email", "Status", "Staff", "Joined"],
136
- "rows": [
137
- [
138
- user.username,
139
- user.email,
140
- "✅ Active" if user.is_active else "❌ Inactive",
141
- "🛡️ Yes" if user.is_staff else "—",
142
- user.date_joined.strftime('%Y-%m-%d %H:%M')
143
- ]
144
- for user in recent_users
145
- ]
146
- }
147
- })
148
- except Exception:
149
- context.update({
150
- "data": {
151
- "headers": [],
152
- "rows": []
153
- }
154
- })
155
-
156
- return context
157
-
158
-
159
- @register_component
160
- class ChartsComponent(BaseComponent):
161
- """
162
- Charts data component for analytics.
163
-
164
- Provides chart data for user registrations and activity.
165
- Supports time range filtering (7/30/90 days).
166
- """
167
-
168
- def get_context_data(self, **kwargs):
169
- """Prepare charts data."""
170
- context = super().get_context_data(**kwargs)
171
-
172
- # Get time range from kwargs (default: 7 days)
173
- days = kwargs.get('days', 7)
174
-
175
- import json
176
- from datetime import datetime, timedelta
177
-
178
- from django.contrib.auth import get_user_model
179
- from django.utils import timezone
180
-
181
- User = get_user_model()
182
-
183
- try:
184
- # Get last N days
185
- today = timezone.now().date()
186
- dates = [(today - timedelta(days=i)).strftime('%Y-%m-%d')
187
- for i in range(days-1, -1, -1)]
188
-
189
- # User registrations
190
- registrations = []
191
- for date_str in dates:
192
- date_obj = datetime.strptime(date_str, '%Y-%m-%d').date()
193
- count = User.objects.filter(date_joined__date=date_obj).count()
194
- registrations.append(count)
195
-
196
- # User activity (last login)
197
- activity = []
198
- for date_str in dates:
199
- date_obj = datetime.strptime(date_str, '%Y-%m-%d').date()
200
- count = User.objects.filter(
201
- last_login__date=date_obj
202
- ).count() if hasattr(User, 'last_login') else 0
203
- activity.append(count)
204
-
205
- # Format labels
206
- day_labels = [d.split('-')[2] for d in dates]
207
-
208
- # Chart data structures
209
- user_reg_data = {
210
- 'labels': day_labels,
211
- 'datasets': [{
212
- 'label': 'New Users',
213
- 'data': registrations,
214
- 'backgroundColor': 'rgba(59, 130, 246, 0.5)',
215
- 'borderColor': 'rgb(59, 130, 246)',
216
- 'borderWidth': 2,
217
- }]
218
- }
219
-
220
- user_activity_data = {
221
- 'labels': day_labels,
222
- 'datasets': [{
223
- 'label': 'Active Users',
224
- 'data': activity,
225
- 'backgroundColor': 'rgba(34, 197, 94, 0.5)',
226
- 'borderColor': 'rgb(34, 197, 94)',
227
- 'borderWidth': 2,
228
- }]
229
- }
230
-
231
- context.update({
232
- "data": {
233
- "registrations": json.dumps(user_reg_data),
234
- "activity": json.dumps(user_activity_data),
235
- }
236
- })
237
- except Exception:
238
- context.update({
239
- "data": {}
240
- })
241
-
242
- return context
243
-
244
-
245
- @register_component
246
- class ActivityTrackerComponent(BaseComponent):
247
- """
248
- Activity tracker component for GitHub-style heatmap.
249
-
250
- Provides 365 days of user activity data with level indicators.
251
- """
252
-
253
- def get_context_data(self, **kwargs):
254
- """Prepare activity tracker data."""
255
- context = super().get_context_data(**kwargs)
256
-
257
- from datetime import timedelta
258
-
259
- from django.contrib.auth import get_user_model
260
- from django.utils import timezone
261
-
262
- User = get_user_model()
263
-
264
- try:
265
- today = timezone.now().date()
266
- activity_data = []
267
-
268
- for days_ago in range(364, -1, -1): # 365 days
269
- date = today - timedelta(days=days_ago)
270
-
271
- # Count registrations
272
- registrations = User.objects.filter(
273
- date_joined__date=date
274
- ).count()
275
-
276
- # Count logins
277
- logins = 0
278
- if hasattr(User, 'last_login'):
279
- logins = User.objects.filter(
280
- last_login__date=date
281
- ).count()
282
-
283
- total_activity = registrations + logins
284
-
285
- activity_data.append({
286
- 'date': date.isoformat(),
287
- 'count': total_activity,
288
- 'level': self._get_activity_level(total_activity),
289
- })
290
-
291
- context.update({
292
- "data": activity_data
293
- })
294
- except Exception:
295
- context.update({
296
- "data": []
297
- })
298
-
299
- return context
300
-
301
- def _get_activity_level(self, count: int) -> int:
302
- """Convert activity count to level (0-4) for heatmap colors."""
303
- if count == 0:
304
- return 0
305
- elif count <= 2:
306
- return 1
307
- elif count <= 5:
308
- return 2
309
- elif count <= 10:
310
- return 3
311
- else:
312
- return 4
@@ -1,174 +0,0 @@
1
- """
2
- Debug utilities for dashboard rendering.
3
-
4
- Saves rendered HTML to disk for inspection and comparison.
5
- """
6
-
7
- import json
8
- from datetime import datetime
9
- from pathlib import Path
10
- from typing import Any, Dict, Optional
11
-
12
-
13
- class DashboardDebugger:
14
- """Save dashboard renders for debugging."""
15
-
16
- def __init__(self, output_dir: Optional[Path] = None):
17
- """
18
- Initialize debugger.
19
-
20
- Args:
21
- output_dir: Directory to save renders (default: django_cfg/debug/dashboard/)
22
- """
23
- if output_dir is None:
24
- # Use django_cfg package directory
25
- django_cfg_root = Path(__file__).parent.parent
26
- output_dir = django_cfg_root / 'debug' / 'dashboard'
27
-
28
- self.output_dir = Path(output_dir)
29
- self.output_dir.mkdir(parents=True, exist_ok=True)
30
-
31
- def save_render(
32
- self,
33
- html: str,
34
- name: str = 'dashboard',
35
- context: Optional[Dict[str, Any]] = None,
36
- metadata: Optional[Dict[str, Any]] = None
37
- ) -> Path:
38
- """
39
- Save rendered HTML with metadata.
40
-
41
- Args:
42
- html: Rendered HTML content
43
- name: Base name for files
44
- context: Template context data
45
- metadata: Additional metadata
46
-
47
- Returns:
48
- Path to saved HTML file
49
- """
50
- timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
51
- base_name = f"{name}_{timestamp}"
52
-
53
- # Save HTML
54
- html_path = self.output_dir / f"{base_name}.html"
55
- html_path.write_text(html, encoding='utf-8')
56
-
57
- # Save context as JSON
58
- if context:
59
- context_path = self.output_dir / f"{base_name}_context.json"
60
- # Convert context to JSON-serializable format
61
- serializable_context = self._make_serializable(context)
62
- context_path.write_text(
63
- json.dumps(serializable_context, indent=2, ensure_ascii=False),
64
- encoding='utf-8'
65
- )
66
-
67
- # Save metadata
68
- meta = {
69
- 'timestamp': timestamp,
70
- 'name': name,
71
- 'html_size': len(html),
72
- 'html_lines': html.count('\n'),
73
- }
74
-
75
- if metadata:
76
- meta.update(metadata)
77
-
78
- meta_path = self.output_dir / f"{base_name}_meta.json"
79
- meta_path.write_text(
80
- json.dumps(meta, indent=2, ensure_ascii=False),
81
- encoding='utf-8'
82
- )
83
-
84
- print(f"✅ Saved dashboard render: {html_path}")
85
- print(f" Context: {context_path if context else 'N/A'}")
86
- print(f" Metadata: {meta_path}")
87
-
88
- return html_path
89
-
90
- def _make_serializable(self, obj: Any) -> Any:
91
- """Convert object to JSON-serializable format."""
92
- if isinstance(obj, dict):
93
- return {k: self._make_serializable(v) for k, v in obj.items()}
94
- elif isinstance(obj, (list, tuple)):
95
- return [self._make_serializable(item) for item in obj]
96
- elif isinstance(obj, (str, int, float, bool, type(None))):
97
- return obj
98
- else:
99
- # For non-serializable objects, use string representation
100
- return str(obj)
101
-
102
- def save_section_render(
103
- self,
104
- section_name: str,
105
- html: str,
106
- section_data: Optional[Dict[str, Any]] = None
107
- ) -> Path:
108
- """
109
- Save individual section render.
110
-
111
- Args:
112
- section_name: Name of section (overview, stats, etc.)
113
- html: Rendered HTML
114
- section_data: Section-specific data
115
-
116
- Returns:
117
- Path to saved file
118
- """
119
- return self.save_render(
120
- html=html,
121
- name=f"section_{section_name}",
122
- context=section_data,
123
- metadata={'section': section_name}
124
- )
125
-
126
- def compare_with_archive(self, current_html: str, archive_path: Path) -> Dict[str, Any]:
127
- """
128
- Compare current render with archived version.
129
-
130
- Args:
131
- current_html: Current rendered HTML
132
- archive_path: Path to archived HTML
133
-
134
- Returns:
135
- Comparison results
136
- """
137
- if not archive_path.exists():
138
- return {
139
- 'error': f"Archive not found: {archive_path}"
140
- }
141
-
142
- archive_html = archive_path.read_text(encoding='utf-8')
143
-
144
- return {
145
- 'current_size': len(current_html),
146
- 'archive_size': len(archive_html),
147
- 'size_diff': len(current_html) - len(archive_html),
148
- 'current_lines': current_html.count('\n'),
149
- 'archive_lines': archive_html.count('\n'),
150
- 'lines_diff': current_html.count('\n') - archive_html.count('\n'),
151
- 'identical': current_html == archive_html,
152
- }
153
-
154
-
155
- # Global instance
156
- _debugger: Optional[DashboardDebugger] = None
157
-
158
-
159
- def get_debugger() -> DashboardDebugger:
160
- """Get or create global debugger instance."""
161
- global _debugger
162
- if _debugger is None:
163
- _debugger = DashboardDebugger()
164
- return _debugger
165
-
166
-
167
- def save_dashboard_render(html: str, **kwargs) -> Path:
168
- """Convenience function to save dashboard render."""
169
- return get_debugger().save_render(html, **kwargs)
170
-
171
-
172
- def save_section_render(section_name: str, html: str, **kwargs) -> Path:
173
- """Convenience function to save section render."""
174
- return get_debugger().save_section_render(section_name, html, **kwargs)