django-cfg 1.4.84__py3-none-any.whl → 1.4.86__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.
- django_cfg/__init__.py +1 -1
- django_cfg/apps/dashboard/serializers/__init__.py +55 -0
- django_cfg/apps/dashboard/serializers/activity.py +38 -0
- django_cfg/apps/dashboard/serializers/apizones.py +26 -0
- django_cfg/apps/dashboard/serializers/base.py +16 -0
- django_cfg/apps/dashboard/serializers/charts.py +44 -0
- django_cfg/apps/dashboard/serializers/commands.py +26 -0
- django_cfg/apps/dashboard/serializers/overview.py +34 -0
- django_cfg/apps/dashboard/serializers/statistics.py +46 -0
- django_cfg/apps/dashboard/serializers/system.py +58 -0
- django_cfg/apps/dashboard/services/__init__.py +10 -1
- django_cfg/apps/dashboard/services/apizones_service.py +119 -0
- django_cfg/apps/dashboard/services/charts_service.py +266 -0
- django_cfg/apps/dashboard/services/commands_service.py +142 -0
- django_cfg/apps/dashboard/services/statistics_service.py +262 -104
- django_cfg/apps/dashboard/urls.py +25 -6
- django_cfg/apps/dashboard/views/__init__.py +23 -0
- django_cfg/apps/dashboard/views/activity_views.py +83 -0
- django_cfg/apps/dashboard/views/apizones_views.py +73 -0
- django_cfg/apps/dashboard/views/charts_views.py +159 -0
- django_cfg/apps/dashboard/views/commands_views.py +73 -0
- django_cfg/apps/dashboard/views/overview_views.py +92 -0
- django_cfg/apps/dashboard/views/statistics_views.py +105 -0
- django_cfg/apps/dashboard/views/system_views.py +73 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/fetchers.ts.jinja +0 -1
- django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/function.ts.jinja +1 -1
- django_cfg/modules/django_unfold/callbacks/main.py +7 -6
- django_cfg/modules/django_unfold/dashboard.py +1 -36
- django_cfg/modules/django_unfold/models/config.py +102 -73
- django_cfg/modules/django_unfold/tailwind.py +31 -79
- django_cfg/pyproject.toml +1 -1
- django_cfg/static/frontend/admin/404.html +1 -1
- django_cfg/static/frontend/admin/500.html +1 -1
- django_cfg/static/frontend/admin/_next/static/chunks/23004-faae121bbfecc163.js +1 -0
- django_cfg/static/frontend/admin/_next/static/chunks/{20695.a7d37b6c40ad3f58.js → 43076.55dd23b6cd68edb0.js} +1 -1
- django_cfg/static/frontend/admin/_next/static/chunks/50314-3b9d15242191c8bc.js +1 -0
- django_cfg/static/frontend/admin/_next/static/chunks/{64330.2ef79bccd7d4e363.js → 64330.41858e98c0e5173b.js} +1 -1
- django_cfg/static/frontend/admin/_next/static/chunks/6766.8d01e44e83070e83.js +1 -0
- django_cfg/static/frontend/admin/_next/static/chunks/{96168.eb7fdb721b9cdb00.js → 96168.b7197f890097df6e.js} +1 -1
- django_cfg/static/frontend/admin/_next/static/chunks/pages/{404-c283223d1afd02a2.js → 404-cf71cd7b3cb005e5.js} +1 -1
- django_cfg/static/frontend/admin/_next/static/chunks/pages/{500-389d6d3e1f2f7fda.js → 500-ff19c7842e3df415.js} +1 -1
- django_cfg/static/frontend/admin/_next/static/chunks/pages/_app-f62e5528fbcbb6b3.js +272 -0
- django_cfg/static/frontend/admin/_next/static/chunks/pages/{_error-5291033275c26d09.js → _error-87f3fdc2aa131e77.js} +1 -1
- django_cfg/static/frontend/admin/_next/static/chunks/pages/{index-d7bc30185f52cbca.js → index-69f737d4802cc5b7.js} +1 -1
- django_cfg/static/frontend/admin/_next/static/chunks/pages/private/centrifugo-f24beb6ed3955aa8.js +1 -0
- django_cfg/static/frontend/admin/_next/static/chunks/pages/private/{profile-e93a65e8e7d9022b.js → profile-b8045f993287f1a7.js} +1 -1
- django_cfg/static/frontend/admin/_next/static/chunks/pages/private/{ui-669e8f2a785beba2.js → ui-373fff8b42878e64.js} +1 -1
- django_cfg/static/frontend/admin/_next/static/chunks/pages/private-fe9faa86ecdb0ce6.js +1 -0
- django_cfg/static/frontend/admin/_next/static/chunks/{webpack-92add5f95c66e349.js → webpack-7c456a65e96eb97e.js} +1 -1
- django_cfg/static/frontend/admin/_next/static/css/5f9a37b6e6a72303.css +3 -0
- django_cfg/static/frontend/admin/_next/static/wg0mGdXjT00H_1BUxoOSH/_buildManifest.js +1 -0
- django_cfg/static/frontend/admin/auth.html +1 -1
- django_cfg/static/frontend/admin/index.html +1 -1
- django_cfg/static/frontend/admin/legal/cookies.html +1 -1
- django_cfg/static/frontend/admin/legal/privacy.html +1 -1
- django_cfg/static/frontend/admin/legal/security.html +1 -1
- django_cfg/static/frontend/admin/legal/terms.html +1 -1
- django_cfg/static/frontend/admin/private/centrifugo.html +1 -1
- django_cfg/static/frontend/admin/private/profile.html +1 -1
- django_cfg/static/frontend/admin/private/ui.html +1 -1
- django_cfg/static/frontend/admin/private.html +1 -1
- django_cfg/templates/admin/index.html +237 -5
- django_cfg/templates/admin/sections/commands_section.html +5 -0
- django_cfg/templates/admin/sections/documentation_section.html +5 -0
- django_cfg/templates/admin/sections/overview_section.html +5 -0
- django_cfg/templates/admin/sections/stats_section.html +5 -0
- django_cfg/templates/admin/sections/system_section.html +5 -0
- django_cfg/templates/admin/sections/widgets_section.html +11 -0
- django_cfg/templates/unfold/layouts/skeleton.html +27 -0
- django_cfg/templatetags/django_cfg.py +53 -0
- {django_cfg-1.4.84.dist-info → django_cfg-1.4.86.dist-info}/METADATA +1 -1
- {django_cfg-1.4.84.dist-info → django_cfg-1.4.86.dist-info}/RECORD +76 -53
- django_cfg/apps/dashboard/api/__init__.py +0 -27
- django_cfg/apps/dashboard/api/serializers.py +0 -165
- django_cfg/apps/dashboard/api/viewsets.py +0 -257
- django_cfg/static/frontend/admin/_next/static/-Zk0eDB7OJOEFrFyR5BwZ/_buildManifest.js +0 -1
- django_cfg/static/frontend/admin/_next/static/chunks/43076-4be6a9794e9c3e8b.js +0 -1
- django_cfg/static/frontend/admin/_next/static/chunks/50314-79c02212788f1ec7.js +0 -1
- django_cfg/static/frontend/admin/_next/static/chunks/6766.d62fed7cd4761148.js +0 -1
- django_cfg/static/frontend/admin/_next/static/chunks/82296-a2c8d38f62224be5.js +0 -1
- django_cfg/static/frontend/admin/_next/static/chunks/pages/_app-f25bec36bbdc9625.js +0 -272
- django_cfg/static/frontend/admin/_next/static/chunks/pages/private/centrifugo-22532c65971225eb.js +0 -1
- django_cfg/static/frontend/admin/_next/static/chunks/pages/private-a8a9ba76f2c75354.js +0 -1
- django_cfg/static/frontend/admin/_next/static/css/78d677ac1677c210.css +0 -3
- /django_cfg/static/frontend/admin/_next/static/{-Zk0eDB7OJOEFrFyR5BwZ → wg0mGdXjT00H_1BUxoOSH}/_ssgManifest.js +0 -0
- {django_cfg-1.4.84.dist-info → django_cfg-1.4.86.dist-info}/WHEEL +0 -0
- {django_cfg-1.4.84.dist-info → django_cfg-1.4.86.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.4.84.dist-info → django_cfg-1.4.86.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
|
+
}
|