django-cfg 1.4.84__py3-none-any.whl → 1.4.85__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 (86) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/dashboard/serializers/__init__.py +55 -0
  3. django_cfg/apps/dashboard/serializers/activity.py +38 -0
  4. django_cfg/apps/dashboard/serializers/apizones.py +26 -0
  5. django_cfg/apps/dashboard/serializers/base.py +16 -0
  6. django_cfg/apps/dashboard/serializers/charts.py +44 -0
  7. django_cfg/apps/dashboard/serializers/commands.py +26 -0
  8. django_cfg/apps/dashboard/serializers/overview.py +34 -0
  9. django_cfg/apps/dashboard/serializers/statistics.py +46 -0
  10. django_cfg/apps/dashboard/serializers/system.py +58 -0
  11. django_cfg/apps/dashboard/services/__init__.py +10 -1
  12. django_cfg/apps/dashboard/services/apizones_service.py +119 -0
  13. django_cfg/apps/dashboard/services/charts_service.py +266 -0
  14. django_cfg/apps/dashboard/services/commands_service.py +142 -0
  15. django_cfg/apps/dashboard/services/statistics_service.py +262 -104
  16. django_cfg/apps/dashboard/urls.py +25 -6
  17. django_cfg/apps/dashboard/views/__init__.py +23 -0
  18. django_cfg/apps/dashboard/views/activity_views.py +83 -0
  19. django_cfg/apps/dashboard/views/apizones_views.py +73 -0
  20. django_cfg/apps/dashboard/views/charts_views.py +159 -0
  21. django_cfg/apps/dashboard/views/commands_views.py +73 -0
  22. django_cfg/apps/dashboard/views/overview_views.py +92 -0
  23. django_cfg/apps/dashboard/views/statistics_views.py +105 -0
  24. django_cfg/apps/dashboard/views/system_views.py +73 -0
  25. django_cfg/modules/django_unfold/callbacks/main.py +7 -6
  26. django_cfg/modules/django_unfold/dashboard.py +1 -36
  27. django_cfg/modules/django_unfold/models/config.py +102 -73
  28. django_cfg/modules/django_unfold/tailwind.py +31 -79
  29. django_cfg/pyproject.toml +1 -1
  30. django_cfg/static/frontend/admin/404.html +1 -1
  31. django_cfg/static/frontend/admin/500.html +1 -1
  32. django_cfg/static/frontend/admin/_next/static/BembwiEtlu4eFl3OX7n1k/_buildManifest.js +1 -0
  33. django_cfg/static/frontend/admin/_next/static/chunks/23004-faae121bbfecc163.js +1 -0
  34. django_cfg/static/frontend/admin/_next/static/chunks/{20695.a7d37b6c40ad3f58.js → 43076.55dd23b6cd68edb0.js} +1 -1
  35. django_cfg/static/frontend/admin/_next/static/chunks/50314-3b9d15242191c8bc.js +1 -0
  36. django_cfg/static/frontend/admin/_next/static/chunks/{64330.2ef79bccd7d4e363.js → 64330.41858e98c0e5173b.js} +1 -1
  37. django_cfg/static/frontend/admin/_next/static/chunks/6766.8d01e44e83070e83.js +1 -0
  38. django_cfg/static/frontend/admin/_next/static/chunks/{96168.eb7fdb721b9cdb00.js → 96168.b7197f890097df6e.js} +1 -1
  39. django_cfg/static/frontend/admin/_next/static/chunks/pages/{404-c283223d1afd02a2.js → 404-cf71cd7b3cb005e5.js} +1 -1
  40. django_cfg/static/frontend/admin/_next/static/chunks/pages/{500-389d6d3e1f2f7fda.js → 500-ff19c7842e3df415.js} +1 -1
  41. django_cfg/static/frontend/admin/_next/static/chunks/pages/_app-f62e5528fbcbb6b3.js +272 -0
  42. django_cfg/static/frontend/admin/_next/static/chunks/pages/{_error-5291033275c26d09.js → _error-87f3fdc2aa131e77.js} +1 -1
  43. django_cfg/static/frontend/admin/_next/static/chunks/pages/{index-d7bc30185f52cbca.js → index-69f737d4802cc5b7.js} +1 -1
  44. django_cfg/static/frontend/admin/_next/static/chunks/pages/private/centrifugo-f24beb6ed3955aa8.js +1 -0
  45. django_cfg/static/frontend/admin/_next/static/chunks/pages/private/{profile-e93a65e8e7d9022b.js → profile-b8045f993287f1a7.js} +1 -1
  46. django_cfg/static/frontend/admin/_next/static/chunks/pages/private/{ui-669e8f2a785beba2.js → ui-373fff8b42878e64.js} +1 -1
  47. django_cfg/static/frontend/admin/_next/static/chunks/pages/private-fe9faa86ecdb0ce6.js +1 -0
  48. django_cfg/static/frontend/admin/_next/static/chunks/{webpack-92add5f95c66e349.js → webpack-7c456a65e96eb97e.js} +1 -1
  49. django_cfg/static/frontend/admin/_next/static/css/5f9a37b6e6a72303.css +3 -0
  50. django_cfg/static/frontend/admin/auth.html +1 -1
  51. django_cfg/static/frontend/admin/index.html +1 -1
  52. django_cfg/static/frontend/admin/legal/cookies.html +1 -1
  53. django_cfg/static/frontend/admin/legal/privacy.html +1 -1
  54. django_cfg/static/frontend/admin/legal/security.html +1 -1
  55. django_cfg/static/frontend/admin/legal/terms.html +1 -1
  56. django_cfg/static/frontend/admin/private/centrifugo.html +1 -1
  57. django_cfg/static/frontend/admin/private/profile.html +1 -1
  58. django_cfg/static/frontend/admin/private/ui.html +1 -1
  59. django_cfg/static/frontend/admin/private.html +1 -1
  60. django_cfg/templates/admin/index.html +237 -5
  61. django_cfg/templates/admin/sections/commands_section.html +5 -0
  62. django_cfg/templates/admin/sections/documentation_section.html +5 -0
  63. django_cfg/templates/admin/sections/overview_section.html +5 -0
  64. django_cfg/templates/admin/sections/stats_section.html +5 -0
  65. django_cfg/templates/admin/sections/system_section.html +5 -0
  66. django_cfg/templates/admin/sections/widgets_section.html +11 -0
  67. django_cfg/templates/unfold/layouts/skeleton.html +27 -0
  68. django_cfg/templatetags/django_cfg.py +53 -0
  69. {django_cfg-1.4.84.dist-info → django_cfg-1.4.85.dist-info}/METADATA +1 -1
  70. {django_cfg-1.4.84.dist-info → django_cfg-1.4.85.dist-info}/RECORD +74 -51
  71. django_cfg/apps/dashboard/api/__init__.py +0 -27
  72. django_cfg/apps/dashboard/api/serializers.py +0 -165
  73. django_cfg/apps/dashboard/api/viewsets.py +0 -257
  74. django_cfg/static/frontend/admin/_next/static/-Zk0eDB7OJOEFrFyR5BwZ/_buildManifest.js +0 -1
  75. django_cfg/static/frontend/admin/_next/static/chunks/43076-4be6a9794e9c3e8b.js +0 -1
  76. django_cfg/static/frontend/admin/_next/static/chunks/50314-79c02212788f1ec7.js +0 -1
  77. django_cfg/static/frontend/admin/_next/static/chunks/6766.d62fed7cd4761148.js +0 -1
  78. django_cfg/static/frontend/admin/_next/static/chunks/82296-a2c8d38f62224be5.js +0 -1
  79. django_cfg/static/frontend/admin/_next/static/chunks/pages/_app-f25bec36bbdc9625.js +0 -272
  80. django_cfg/static/frontend/admin/_next/static/chunks/pages/private/centrifugo-22532c65971225eb.js +0 -1
  81. django_cfg/static/frontend/admin/_next/static/chunks/pages/private-a8a9ba76f2c75354.js +0 -1
  82. django_cfg/static/frontend/admin/_next/static/css/78d677ac1677c210.css +0 -3
  83. /django_cfg/static/frontend/admin/_next/static/{-Zk0eDB7OJOEFrFyR5BwZ → BembwiEtlu4eFl3OX7n1k}/_ssgManifest.js +0 -0
  84. {django_cfg-1.4.84.dist-info → django_cfg-1.4.85.dist-info}/WHEEL +0 -0
  85. {django_cfg-1.4.84.dist-info → django_cfg-1.4.85.dist-info}/entry_points.txt +0 -0
  86. {django_cfg-1.4.84.dist-info → django_cfg-1.4.85.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,266 @@
1
+ """
2
+ Charts Service
3
+
4
+ Provides chart data for dashboard analytics.
5
+ Includes user registration charts, activity charts, and activity tracker.
6
+ """
7
+
8
+ import logging
9
+ from datetime import timedelta
10
+ from typing import Any, Dict, List
11
+
12
+ from django.contrib.auth import get_user_model
13
+ from django.db.models import Count
14
+ from django.utils import timezone
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ class ChartsService:
20
+ """
21
+ Service for generating chart data.
22
+
23
+ %%PRIORITY:MEDIUM%%
24
+ %%AI_HINT: Generates time-series data for charts and activity tracking%%
25
+
26
+ TAGS: charts, analytics, dashboard, service
27
+ """
28
+
29
+ def __init__(self):
30
+ """Initialize charts service."""
31
+ self.logger = logger
32
+
33
+ def _get_user_model(self):
34
+ """Get the user model safely."""
35
+ return get_user_model()
36
+
37
+ def _get_empty_chart_data(self, label: str) -> Dict[str, Any]:
38
+ """Get empty chart data structure."""
39
+ return {
40
+ "labels": ["No Data"],
41
+ "datasets": [
42
+ {
43
+ "label": label,
44
+ "data": [0],
45
+ "backgroundColor": "rgba(156, 163, 175, 0.1)",
46
+ "borderColor": "rgb(156, 163, 175)",
47
+ "tension": 0.4
48
+ }
49
+ ]
50
+ }
51
+
52
+ def get_user_registration_chart(self, days: int = 7) -> Dict[str, Any]:
53
+ """
54
+ Get user registration chart data for last N days.
55
+
56
+ Args:
57
+ days: Number of days to include in chart (default 7)
58
+
59
+ Returns:
60
+ Chart.js compatible data structure
61
+
62
+ %%AI_HINT: Real data aggregated by date from User.date_joined%%
63
+ """
64
+ try:
65
+ User = self._get_user_model()
66
+
67
+ # Get date range
68
+ end_date = timezone.now().date()
69
+ start_date = end_date - timedelta(days=days - 1)
70
+
71
+ # Generate date range
72
+ date_range = []
73
+ current_date = start_date
74
+ while current_date <= end_date:
75
+ date_range.append(current_date)
76
+ current_date += timedelta(days=1)
77
+
78
+ # Get registration counts by date
79
+ registration_data = (
80
+ User.objects.filter(date_joined__date__gte=start_date)
81
+ .extra({'date': "date(date_joined)"})
82
+ .values('date')
83
+ .annotate(count=Count('id'))
84
+ .order_by('date')
85
+ )
86
+
87
+ # Create data dictionary for easy lookup
88
+ data_dict = {item['date']: item['count'] for item in registration_data}
89
+
90
+ # Build chart data
91
+ labels = [date.strftime("%m/%d") for date in date_range]
92
+ data_points = [data_dict.get(date, 0) for date in date_range]
93
+
94
+ return {
95
+ "labels": labels,
96
+ "datasets": [
97
+ {
98
+ "label": "New Users",
99
+ "data": data_points,
100
+ "backgroundColor": "rgba(59, 130, 246, 0.1)",
101
+ "borderColor": "rgb(59, 130, 246)",
102
+ "tension": 0.4,
103
+ "fill": True
104
+ }
105
+ ]
106
+ }
107
+
108
+ except Exception as e:
109
+ self.logger.error(f"Error getting user registration chart: {e}")
110
+ return self._get_empty_chart_data("New Users")
111
+
112
+ def get_user_activity_chart(self, days: int = 7) -> Dict[str, Any]:
113
+ """
114
+ Get user activity chart data for last N days.
115
+
116
+ Args:
117
+ days: Number of days to include in chart (default 7)
118
+
119
+ Returns:
120
+ Chart.js compatible data structure
121
+
122
+ %%AI_HINT: Real data aggregated by date from User.last_login%%
123
+ """
124
+ try:
125
+ User = self._get_user_model()
126
+
127
+ # Get date range
128
+ end_date = timezone.now().date()
129
+ start_date = end_date - timedelta(days=days - 1)
130
+
131
+ # Generate date range
132
+ date_range = []
133
+ current_date = start_date
134
+ while current_date <= end_date:
135
+ date_range.append(current_date)
136
+ current_date += timedelta(days=1)
137
+
138
+ # Get login activity (users who logged in each day)
139
+ activity_data = (
140
+ User.objects.filter(last_login__date__gte=start_date, last_login__isnull=False)
141
+ .extra({'date': "date(last_login)"})
142
+ .values('date')
143
+ .annotate(count=Count('id'))
144
+ .order_by('date')
145
+ )
146
+
147
+ # Create data dictionary for easy lookup
148
+ data_dict = {item['date']: item['count'] for item in activity_data}
149
+
150
+ # Build chart data
151
+ labels = [date.strftime("%m/%d") for date in date_range]
152
+ data_points = [data_dict.get(date, 0) for date in date_range]
153
+
154
+ return {
155
+ "labels": labels,
156
+ "datasets": [
157
+ {
158
+ "label": "Active Users",
159
+ "data": data_points,
160
+ "backgroundColor": "rgba(34, 197, 94, 0.1)",
161
+ "borderColor": "rgb(34, 197, 94)",
162
+ "tension": 0.4,
163
+ "fill": True
164
+ }
165
+ ]
166
+ }
167
+
168
+ except Exception as e:
169
+ self.logger.error(f"Error getting user activity chart: {e}")
170
+ return self._get_empty_chart_data("Active Users")
171
+
172
+ def get_activity_tracker(self, weeks: int = 52) -> List[Dict[str, Any]]:
173
+ """
174
+ Get activity tracker data (GitHub-style contribution graph).
175
+
176
+ Args:
177
+ weeks: Number of weeks to include (default 52)
178
+
179
+ Returns:
180
+ List of day objects with activity levels
181
+
182
+ %%AI_HINT: 365 days of activity data for heatmap visualization%%
183
+ """
184
+ try:
185
+ User = self._get_user_model()
186
+
187
+ # Get data for specified weeks
188
+ days = weeks * 7
189
+ end_date = timezone.now().date()
190
+ start_date = end_date - timedelta(days=days - 1)
191
+
192
+ # Get activity data by date
193
+ activity_data = (
194
+ User.objects.filter(last_login__date__gte=start_date, last_login__isnull=False)
195
+ .extra({'date': "date(last_login)"})
196
+ .values('date')
197
+ .annotate(count=Count('id'))
198
+ .order_by('date')
199
+ )
200
+
201
+ # Create data dictionary for easy lookup
202
+ data_dict = {item['date']: item['count'] for item in activity_data}
203
+
204
+ # Generate tracker data for each day
205
+ tracker_data = []
206
+ current_date = start_date
207
+
208
+ while current_date <= end_date:
209
+ activity_count = data_dict.get(current_date, 0)
210
+
211
+ # Determine level based on activity count
212
+ if activity_count == 0:
213
+ level = 0
214
+ color = "#ebedf0"
215
+ intensity = "No activity"
216
+ elif activity_count <= 2:
217
+ level = 1
218
+ color = "#9be9a8"
219
+ intensity = "Low"
220
+ elif activity_count <= 5:
221
+ level = 2
222
+ color = "#40c463"
223
+ intensity = "Medium"
224
+ elif activity_count <= 10:
225
+ level = 3
226
+ color = "#30a14e"
227
+ intensity = "High"
228
+ else:
229
+ level = 4
230
+ color = "#216e39"
231
+ intensity = "Very high"
232
+
233
+ tracker_data.append({
234
+ "date": current_date.isoformat(),
235
+ "count": activity_count,
236
+ "level": level,
237
+ "color": color,
238
+ "tooltip": f"{current_date.strftime('%Y-%m-%d')}: {activity_count} active users ({intensity})"
239
+ })
240
+
241
+ current_date += timedelta(days=1)
242
+
243
+ return tracker_data
244
+
245
+ except Exception as e:
246
+ self.logger.error(f"Error getting activity tracker: {e}")
247
+ return self._get_empty_tracker_data(weeks * 7)
248
+
249
+ def _get_empty_tracker_data(self, days: int) -> List[Dict[str, Any]]:
250
+ """Get empty tracker data."""
251
+ tracker_data = []
252
+ end_date = timezone.now().date()
253
+ start_date = end_date - timedelta(days=days - 1)
254
+ current_date = start_date
255
+
256
+ while current_date <= end_date:
257
+ tracker_data.append({
258
+ "date": current_date.isoformat(),
259
+ "count": 0,
260
+ "level": 0,
261
+ "color": "#ebedf0",
262
+ "tooltip": f"{current_date.strftime('%Y-%m-%d')}: No data"
263
+ })
264
+ current_date += timedelta(days=1)
265
+
266
+ return tracker_data
@@ -0,0 +1,142 @@
1
+ """
2
+ Commands Service
3
+
4
+ Django management commands discovery and documentation.
5
+ """
6
+
7
+ import logging
8
+ from typing import Any, Dict, List
9
+
10
+ from django.core.management import get_commands, load_command_class
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ class CommandsService:
16
+ """
17
+ Service for Django management commands.
18
+
19
+ %%PRIORITY:LOW%%
20
+ %%AI_HINT: Discovers available Django management commands%%
21
+
22
+ TAGS: commands, django, management, service
23
+ """
24
+
25
+ def __init__(self):
26
+ """Initialize commands service."""
27
+ self.logger = logger
28
+
29
+ def get_all_commands(self) -> List[Dict[str, Any]]:
30
+ """
31
+ Get all available Django management commands.
32
+
33
+ Returns:
34
+ List of command dictionaries with name, app, help text
35
+
36
+ %%AI_HINT: Uses Django's get_commands() for command discovery%%
37
+ """
38
+ try:
39
+ commands_dict = get_commands()
40
+ commands_list = []
41
+
42
+ for command_name, app_name in commands_dict.items():
43
+ try:
44
+ # Try to load command to get help text
45
+ command = load_command_class(app_name, command_name)
46
+ help_text = getattr(command, 'help', 'No description available')
47
+
48
+ # Determine if it's a core Django command or custom
49
+ is_core = app_name.startswith('django.')
50
+ is_custom = not is_core
51
+
52
+ commands_list.append({
53
+ 'name': command_name,
54
+ 'app': app_name,
55
+ 'help': help_text,
56
+ 'is_core': is_core,
57
+ 'is_custom': is_custom,
58
+ })
59
+ except Exception as e:
60
+ # If we can't load the command, still include basic info
61
+ self.logger.debug(f"Could not load command {command_name}: {e}")
62
+ commands_list.append({
63
+ 'name': command_name,
64
+ 'app': app_name,
65
+ 'help': 'Description unavailable',
66
+ 'is_core': app_name.startswith('django.'),
67
+ 'is_custom': not app_name.startswith('django.'),
68
+ })
69
+
70
+ # Sort by name
71
+ commands_list.sort(key=lambda x: x['name'])
72
+ return commands_list
73
+
74
+ except Exception as e:
75
+ self.logger.error(f"Error getting commands: {e}")
76
+ return []
77
+
78
+ def get_commands_by_category(self) -> Dict[str, List[Dict[str, Any]]]:
79
+ """
80
+ Get commands organized by category.
81
+
82
+ Returns:
83
+ Dictionary with categories as keys and command lists as values
84
+
85
+ %%AI_HINT: Categorizes commands by Django core vs custom apps%%
86
+ """
87
+ try:
88
+ all_commands = self.get_all_commands()
89
+
90
+ categorized = {
91
+ 'Django Core': [],
92
+ 'Custom': [],
93
+ 'Third Party': [],
94
+ }
95
+
96
+ for cmd in all_commands:
97
+ app_name = cmd['app']
98
+
99
+ if app_name.startswith('django.'):
100
+ categorized['Django Core'].append(cmd)
101
+ elif app_name.startswith('django_cfg'):
102
+ categorized['Custom'].append(cmd)
103
+ else:
104
+ categorized['Third Party'].append(cmd)
105
+
106
+ # Remove empty categories
107
+ return {k: v for k, v in categorized.items() if v}
108
+
109
+ except Exception as e:
110
+ self.logger.error(f"Error categorizing commands: {e}")
111
+ return {}
112
+
113
+ def get_commands_summary(self) -> Dict[str, Any]:
114
+ """
115
+ Get summary statistics about commands.
116
+
117
+ Returns:
118
+ Dictionary with total, core, and custom command counts
119
+ """
120
+ try:
121
+ all_commands = self.get_all_commands()
122
+ categorized = self.get_commands_by_category()
123
+
124
+ return {
125
+ 'total_commands': len(all_commands),
126
+ 'core_commands': len([c for c in all_commands if c['is_core']]),
127
+ 'custom_commands': len([c for c in all_commands if c['is_custom']]),
128
+ 'categories': list(categorized.keys()),
129
+ 'commands': all_commands,
130
+ 'categorized': categorized,
131
+ }
132
+
133
+ except Exception as e:
134
+ self.logger.error(f"Error getting commands summary: {e}")
135
+ return {
136
+ 'total_commands': 0,
137
+ 'core_commands': 0,
138
+ 'custom_commands': 0,
139
+ 'categories': [],
140
+ 'commands': [],
141
+ 'categorized': {},
142
+ }