django-cfg 1.2.6__py3-none-any.whl → 1.2.8__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.
- django_cfg/__init__.py +1 -1
- django_cfg/apps/agents/admin/execution_admin.py +9 -2
- django_cfg/apps/agents/admin/registry_admin.py +10 -2
- django_cfg/apps/agents/admin/toolsets_admin.py +14 -3
- django_cfg/apps/knowbase/admin/archive_admin.py +3 -2
- django_cfg/apps/knowbase/admin/chat_admin.py +3 -2
- django_cfg/apps/knowbase/admin/document_admin.py +11 -2
- django_cfg/apps/knowbase/admin/external_data_admin.py +2 -1
- django_cfg/apps/urls.py +2 -2
- django_cfg/modules/django_import_export/__init__.py +12 -5
- django_cfg/modules/django_unfold/callbacks/__init__.py +9 -0
- django_cfg/modules/django_unfold/callbacks/actions.py +50 -0
- django_cfg/modules/django_unfold/callbacks/base.py +98 -0
- django_cfg/modules/django_unfold/callbacks/charts.py +224 -0
- django_cfg/modules/django_unfold/callbacks/commands.py +40 -0
- django_cfg/modules/django_unfold/callbacks/main.py +191 -0
- django_cfg/modules/django_unfold/callbacks/revolution.py +76 -0
- django_cfg/modules/django_unfold/callbacks/statistics.py +240 -0
- django_cfg/modules/django_unfold/callbacks/system.py +180 -0
- django_cfg/modules/django_unfold/callbacks/users.py +65 -0
- django_cfg/modules/django_unfold/models/config.py +10 -3
- django_cfg/modules/django_unfold/tailwind.py +68 -0
- django_cfg/templates/admin/components/action_grid.html +49 -0
- django_cfg/templates/admin/components/card.html +50 -0
- django_cfg/templates/admin/components/data_table.html +67 -0
- django_cfg/templates/admin/components/metric_card.html +39 -0
- django_cfg/templates/admin/components/modal.html +58 -0
- django_cfg/templates/admin/components/progress_bar.html +25 -0
- django_cfg/templates/admin/components/section_header.html +26 -0
- django_cfg/templates/admin/components/stat_item.html +32 -0
- django_cfg/templates/admin/components/stats_grid.html +72 -0
- django_cfg/templates/admin/components/status_badge.html +28 -0
- django_cfg/templates/admin/components/user_avatar.html +27 -0
- django_cfg/templates/admin/layouts/dashboard_with_tabs.html +7 -7
- django_cfg/templates/admin/snippets/components/activity_tracker.html +48 -11
- django_cfg/templates/admin/snippets/components/charts_section.html +63 -13
- django_cfg/templates/admin/snippets/components/django_commands.html +18 -18
- django_cfg/templates/admin/snippets/components/quick_actions.html +3 -47
- django_cfg/templates/admin/snippets/components/recent_activity.html +28 -38
- django_cfg/templates/admin/snippets/components/recent_users_table.html +22 -53
- django_cfg/templates/admin/snippets/components/stats_cards.html +2 -66
- django_cfg/templates/admin/snippets/components/system_health.html +13 -63
- django_cfg/templates/admin/snippets/components/system_metrics.html +8 -25
- django_cfg/templates/admin/snippets/tabs/commands_tab.html +1 -1
- django_cfg/templates/admin/snippets/tabs/overview_tab.html +4 -4
- django_cfg/templates/admin/snippets/zones/zones_table.html +12 -33
- django_cfg/templatetags/django_cfg.py +2 -1
- {django_cfg-1.2.6.dist-info → django_cfg-1.2.8.dist-info}/METADATA +2 -1
- {django_cfg-1.2.6.dist-info → django_cfg-1.2.8.dist-info}/RECORD +52 -32
- django_cfg/modules/django_unfold/callbacks.py +0 -795
- {django_cfg-1.2.6.dist-info → django_cfg-1.2.8.dist-info}/WHEEL +0 -0
- {django_cfg-1.2.6.dist-info → django_cfg-1.2.8.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.2.6.dist-info → django_cfg-1.2.8.dist-info}/licenses/LICENSE +0 -0
django_cfg/__init__.py
CHANGED
@@ -17,6 +17,7 @@ from unfold.decorators import display, action
|
|
17
17
|
from unfold.enums import ActionVariant
|
18
18
|
from unfold.contrib.filters.admin import AutocompleteSelectFilter, AutocompleteSelectMultipleFilter
|
19
19
|
from unfold.contrib.forms.widgets import WysiwygWidget
|
20
|
+
from django_cfg import ExportMixin, ExportForm
|
20
21
|
|
21
22
|
from ..models.execution import AgentExecution, WorkflowExecution
|
22
23
|
|
@@ -90,9 +91,12 @@ class AgentExecutionInlineForWorkflow(TabularInline):
|
|
90
91
|
|
91
92
|
|
92
93
|
@admin.register(AgentExecution)
|
93
|
-
class AgentExecutionAdmin(ModelAdmin):
|
94
|
+
class AgentExecutionAdmin(ModelAdmin, ExportMixin):
|
94
95
|
"""Admin interface for AgentExecution with Unfold styling."""
|
95
96
|
|
97
|
+
# Export-only configuration
|
98
|
+
export_form_class = ExportForm
|
99
|
+
|
96
100
|
list_display = [
|
97
101
|
'id_display', 'agent_name_display', 'status_badge', 'user',
|
98
102
|
'execution_metrics', 'cost_display', 'cached_badge', 'created_at'
|
@@ -261,9 +265,12 @@ class AgentExecutionAdmin(ModelAdmin):
|
|
261
265
|
|
262
266
|
|
263
267
|
@admin.register(WorkflowExecution)
|
264
|
-
class WorkflowExecutionAdmin(ModelAdmin):
|
268
|
+
class WorkflowExecutionAdmin(ModelAdmin, ExportMixin):
|
265
269
|
"""Admin interface for WorkflowExecution with Unfold styling."""
|
266
270
|
|
271
|
+
# Export-only configuration
|
272
|
+
export_form_class = ExportForm
|
273
|
+
|
267
274
|
list_display = [
|
268
275
|
'id_display', 'name_display', 'pattern_badge', 'status_badge', 'user',
|
269
276
|
'progress_display', 'metrics_display', 'cost_display', 'created_at'
|
@@ -17,6 +17,7 @@ from unfold.decorators import display, action
|
|
17
17
|
from unfold.enums import ActionVariant
|
18
18
|
from unfold.contrib.filters.admin import AutocompleteSelectFilter, AutocompleteSelectMultipleFilter
|
19
19
|
from unfold.contrib.forms.widgets import WysiwygWidget
|
20
|
+
from django_cfg import ImportExportModelAdmin, ExportMixin, ImportForm, ExportForm
|
20
21
|
|
21
22
|
from ..models.registry import AgentDefinition, AgentTemplate
|
22
23
|
from ..models.execution import AgentExecution
|
@@ -91,9 +92,13 @@ class AgentExecutionInline(TabularInline):
|
|
91
92
|
|
92
93
|
|
93
94
|
@admin.register(AgentDefinition)
|
94
|
-
class AgentDefinitionAdmin(ModelAdmin):
|
95
|
+
class AgentDefinitionAdmin(ModelAdmin, ImportExportModelAdmin):
|
95
96
|
"""Admin interface for AgentDefinition with Unfold styling."""
|
96
97
|
|
98
|
+
# Import/Export configuration
|
99
|
+
import_form_class = ImportForm
|
100
|
+
export_form_class = ExportForm
|
101
|
+
|
97
102
|
list_display = [
|
98
103
|
'name_display', 'display_name', 'category_badge', 'status_badges',
|
99
104
|
'usage_stats', 'performance_indicator', 'created_by', 'created_at'
|
@@ -271,9 +276,12 @@ class AgentDefinitionAdmin(ModelAdmin):
|
|
271
276
|
|
272
277
|
|
273
278
|
@admin.register(AgentTemplate)
|
274
|
-
class AgentTemplateAdmin(ModelAdmin):
|
279
|
+
class AgentTemplateAdmin(ModelAdmin, ExportMixin):
|
275
280
|
"""Admin interface for AgentTemplate with Unfold styling."""
|
276
281
|
|
282
|
+
# Export-only configuration
|
283
|
+
export_form_class = ExportForm
|
284
|
+
|
277
285
|
list_display = ['name_display', 'category_badge', 'status_badge', 'use_cases_preview', 'created_by', 'created_at']
|
278
286
|
ordering = ['-created_at']
|
279
287
|
list_filter = [
|
@@ -17,14 +17,18 @@ from unfold.decorators import display, action
|
|
17
17
|
from unfold.enums import ActionVariant
|
18
18
|
from unfold.contrib.filters.admin import AutocompleteSelectFilter, AutocompleteSelectMultipleFilter
|
19
19
|
from unfold.contrib.forms.widgets import WysiwygWidget
|
20
|
+
from django_cfg import ExportMixin, ImportExportModelAdmin, ImportForm, ExportForm
|
20
21
|
|
21
22
|
from ..models.toolsets import ToolExecution, ApprovalLog, ToolsetConfiguration
|
22
23
|
|
23
24
|
|
24
25
|
@admin.register(ToolExecution)
|
25
|
-
class ToolExecutionAdmin(ModelAdmin):
|
26
|
+
class ToolExecutionAdmin(ModelAdmin, ExportMixin):
|
26
27
|
"""Admin interface for ToolExecution with Unfold styling."""
|
27
28
|
|
29
|
+
# Export-only configuration
|
30
|
+
export_form_class = ExportForm
|
31
|
+
|
28
32
|
list_display = [
|
29
33
|
'id_display', 'tool_name_display', 'toolset_badge', 'status_badge', 'user',
|
30
34
|
'execution_metrics', 'retry_badge', 'created_at'
|
@@ -197,9 +201,12 @@ class ToolExecutionAdmin(ModelAdmin):
|
|
197
201
|
|
198
202
|
|
199
203
|
@admin.register(ApprovalLog)
|
200
|
-
class ApprovalLogAdmin(ModelAdmin):
|
204
|
+
class ApprovalLogAdmin(ModelAdmin, ExportMixin):
|
201
205
|
"""Admin interface for ApprovalLog with Unfold styling."""
|
202
206
|
|
207
|
+
# Export-only configuration
|
208
|
+
export_form_class = ExportForm
|
209
|
+
|
203
210
|
list_display = [
|
204
211
|
'approval_id_display', 'tool_name_display', 'status_badge', 'user',
|
205
212
|
'decision_info', 'time_metrics', 'expiry_status', 'requested_at'
|
@@ -371,9 +378,13 @@ class ApprovalLogAdmin(ModelAdmin):
|
|
371
378
|
|
372
379
|
|
373
380
|
@admin.register(ToolsetConfiguration)
|
374
|
-
class ToolsetConfigurationAdmin(ModelAdmin):
|
381
|
+
class ToolsetConfigurationAdmin(ModelAdmin, ImportExportModelAdmin):
|
375
382
|
"""Admin interface for ToolsetConfiguration with Unfold styling."""
|
376
383
|
|
384
|
+
# Import/Export configuration
|
385
|
+
import_form_class = ImportForm
|
386
|
+
export_form_class = ExportForm
|
387
|
+
|
377
388
|
list_display = [
|
378
389
|
'name_display', 'toolset_class_badge', 'status_badge', 'usage_info', 'created_by', 'created_at'
|
379
390
|
]
|
@@ -18,6 +18,7 @@ from unfold.decorators import display, action
|
|
18
18
|
from unfold.enums import ActionVariant
|
19
19
|
from unfold.contrib.filters.admin import AutocompleteSelectFilter
|
20
20
|
from unfold.contrib.forms.widgets import WysiwygWidget
|
21
|
+
from django_cfg import ExportMixin
|
21
22
|
|
22
23
|
from ..models.archive import DocumentArchive, ArchiveItem, ArchiveItemChunk
|
23
24
|
|
@@ -76,7 +77,7 @@ class ArchiveItemInline(TabularInline):
|
|
76
77
|
|
77
78
|
|
78
79
|
@admin.register(DocumentArchive)
|
79
|
-
class DocumentArchiveAdmin(ModelAdmin):
|
80
|
+
class DocumentArchiveAdmin(ExportMixin, ModelAdmin):
|
80
81
|
"""Admin interface for DocumentArchive."""
|
81
82
|
|
82
83
|
list_display = [
|
@@ -483,7 +484,7 @@ class DocumentArchiveAdmin(ModelAdmin):
|
|
483
484
|
|
484
485
|
|
485
486
|
@admin.register(ArchiveItem)
|
486
|
-
class ArchiveItemAdmin(ModelAdmin):
|
487
|
+
class ArchiveItemAdmin(ExportMixin, ModelAdmin):
|
487
488
|
"""Admin interface for ArchiveItem."""
|
488
489
|
|
489
490
|
list_display = [
|
@@ -10,6 +10,7 @@ from django.db.models import Count, Sum, Avg, Q
|
|
10
10
|
from unfold.admin import ModelAdmin, TabularInline
|
11
11
|
from unfold.decorators import display
|
12
12
|
from unfold.contrib.filters.admin import AutocompleteSelectFilter
|
13
|
+
from django_cfg import ExportMixin
|
13
14
|
|
14
15
|
from ..models import ChatSession, ChatMessage
|
15
16
|
|
@@ -99,7 +100,7 @@ class ChatMessageInline(TabularInline):
|
|
99
100
|
|
100
101
|
|
101
102
|
@admin.register(ChatSession)
|
102
|
-
class ChatSessionAdmin(ModelAdmin):
|
103
|
+
class ChatSessionAdmin(ModelAdmin, ExportMixin):
|
103
104
|
"""Admin interface for ChatSession model with Unfold styling."""
|
104
105
|
|
105
106
|
list_display = [
|
@@ -224,7 +225,7 @@ class ChatSessionAdmin(ModelAdmin):
|
|
224
225
|
|
225
226
|
|
226
227
|
@admin.register(ChatMessage)
|
227
|
-
class ChatMessageAdmin(ModelAdmin):
|
228
|
+
class ChatMessageAdmin(ModelAdmin, ExportMixin):
|
228
229
|
"""Admin interface for ChatMessage model with Unfold styling."""
|
229
230
|
|
230
231
|
list_display = [
|
@@ -15,6 +15,7 @@ from unfold.admin import ModelAdmin, TabularInline
|
|
15
15
|
from unfold.decorators import display
|
16
16
|
from unfold.contrib.filters.admin import AutocompleteSelectFilter, AutocompleteSelectMultipleFilter
|
17
17
|
from unfold.contrib.forms.widgets import WysiwygWidget
|
18
|
+
from django_cfg import ImportExportModelAdmin, ExportMixin, ImportForm, ExportForm
|
18
19
|
|
19
20
|
from ..models import Document, DocumentChunk, DocumentCategory
|
20
21
|
|
@@ -77,9 +78,13 @@ class DocumentChunkInline(TabularInline):
|
|
77
78
|
|
78
79
|
|
79
80
|
@admin.register(Document)
|
80
|
-
class DocumentAdmin(ModelAdmin):
|
81
|
+
class DocumentAdmin(ModelAdmin, ImportExportModelAdmin):
|
81
82
|
"""Admin interface for Document model with Unfold styling."""
|
82
83
|
|
84
|
+
# Import/Export configuration
|
85
|
+
import_form_class = ImportForm
|
86
|
+
export_form_class = ExportForm
|
87
|
+
|
83
88
|
list_display = [
|
84
89
|
'title_display', 'categories_display', 'user',
|
85
90
|
'visibility_badge', 'status_badge', 'chunks_count_display', 'vectorization_progress', 'tokens_display', 'cost_display', 'created_at'
|
@@ -568,9 +573,13 @@ class DocumentChunkAdmin(ModelAdmin):
|
|
568
573
|
|
569
574
|
|
570
575
|
@admin.register(DocumentCategory)
|
571
|
-
class DocumentCategoryAdmin(ModelAdmin):
|
576
|
+
class DocumentCategoryAdmin(ModelAdmin, ImportExportModelAdmin):
|
572
577
|
"""Admin interface for DocumentCategory model with Unfold styling."""
|
573
578
|
|
579
|
+
# Import/Export configuration
|
580
|
+
import_form_class = ImportForm
|
581
|
+
export_form_class = ExportForm
|
582
|
+
|
574
583
|
list_display = [
|
575
584
|
'short_uuid', 'name', 'visibility_badge', 'document_count', 'created_at'
|
576
585
|
]
|
@@ -18,6 +18,7 @@ from unfold.decorators import display, action
|
|
18
18
|
from unfold.enums import ActionVariant
|
19
19
|
from unfold.contrib.filters.admin import AutocompleteSelectFilter, AutocompleteSelectMultipleFilter
|
20
20
|
from unfold.contrib.forms.widgets import WysiwygWidget
|
21
|
+
from django_cfg import ExportMixin
|
21
22
|
|
22
23
|
from ..models.external_data import ExternalData, ExternalDataChunk, ExternalDataType, ExternalDataStatus
|
23
24
|
|
@@ -80,7 +81,7 @@ class ExternalDataChunkInline(TabularInline):
|
|
80
81
|
|
81
82
|
|
82
83
|
@admin.register(ExternalData)
|
83
|
-
class ExternalDataAdmin(ModelAdmin):
|
84
|
+
class ExternalDataAdmin(ModelAdmin, ExportMixin):
|
84
85
|
"""Admin interface for ExternalData model with Unfold styling."""
|
85
86
|
|
86
87
|
list_display = [
|
django_cfg/apps/urls.py
CHANGED
@@ -45,8 +45,8 @@ def get_django_cfg_urlpatterns() -> List[URLPattern]:
|
|
45
45
|
# patterns.append(path('leads/', include('django_cfg.apps.leads.urls')))
|
46
46
|
|
47
47
|
# Tasks app - enabled when knowbase or agents are enabled
|
48
|
-
if base_module.is_tasks_enabled():
|
49
|
-
|
48
|
+
# if base_module.is_tasks_enabled():
|
49
|
+
# patterns.append(path('tasks/', include('django_cfg.apps.tasks.urls')))
|
50
50
|
|
51
51
|
except Exception:
|
52
52
|
# Fallback: include all URLs if config is not available
|
@@ -8,27 +8,34 @@ Provides seamless integration without unnecessary wrappers.
|
|
8
8
|
# Re-export original classes through django-cfg registry
|
9
9
|
from import_export.admin import ImportExportMixin as BaseImportExportMixin, ImportExportModelAdmin as BaseImportExportModelAdmin, ExportMixin as BaseExportMixin, ImportMixin as BaseImportMixin
|
10
10
|
from import_export.resources import ModelResource as BaseResource
|
11
|
-
|
11
|
+
# Use Unfold styled forms instead of default ones
|
12
|
+
from unfold.contrib.import_export.forms import ImportForm, ExportForm, SelectableFieldsExportForm
|
12
13
|
|
13
14
|
|
14
15
|
class ImportExportMixin(BaseImportExportMixin):
|
15
|
-
"""Django-CFG enhanced ImportExportMixin with custom templates."""
|
16
|
+
"""Django-CFG enhanced ImportExportMixin with custom templates and Unfold forms."""
|
16
17
|
change_list_template = 'admin/import_export/change_list_import_export.html'
|
18
|
+
import_form_class = ImportForm
|
19
|
+
export_form_class = ExportForm
|
17
20
|
|
18
21
|
|
19
22
|
class ImportExportModelAdmin(BaseImportExportModelAdmin):
|
20
|
-
"""Django-CFG enhanced ImportExportModelAdmin with custom templates."""
|
23
|
+
"""Django-CFG enhanced ImportExportModelAdmin with custom templates and Unfold forms."""
|
21
24
|
change_list_template = 'admin/import_export/change_list_import_export.html'
|
25
|
+
import_form_class = ImportForm
|
26
|
+
export_form_class = ExportForm
|
22
27
|
|
23
28
|
|
24
29
|
class ExportMixin(BaseExportMixin):
|
25
|
-
"""Django-CFG enhanced ExportMixin with custom templates."""
|
30
|
+
"""Django-CFG enhanced ExportMixin with custom templates and Unfold forms."""
|
26
31
|
change_list_template = 'admin/import_export/change_list_export.html'
|
32
|
+
export_form_class = ExportForm
|
27
33
|
|
28
34
|
|
29
35
|
class ImportMixin(BaseImportMixin):
|
30
|
-
"""Django-CFG enhanced ImportMixin with custom templates."""
|
36
|
+
"""Django-CFG enhanced ImportMixin with custom templates and Unfold forms."""
|
31
37
|
change_list_template = 'admin/import_export/change_list_import.html'
|
38
|
+
import_form_class = ImportForm
|
32
39
|
|
33
40
|
|
34
41
|
__all__ = [
|
@@ -0,0 +1,50 @@
|
|
1
|
+
"""
|
2
|
+
Quick actions callbacks.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import logging
|
6
|
+
from typing import List
|
7
|
+
|
8
|
+
from ..models.dashboard import QuickAction
|
9
|
+
from ..icons import Icons
|
10
|
+
from .base import get_user_admin_urls
|
11
|
+
|
12
|
+
logger = logging.getLogger(__name__)
|
13
|
+
|
14
|
+
|
15
|
+
class ActionsCallbacks:
|
16
|
+
"""Quick actions callbacks."""
|
17
|
+
|
18
|
+
def get_quick_actions(self) -> List[QuickAction]:
|
19
|
+
"""Get quick action buttons as Pydantic models."""
|
20
|
+
# Get user admin URLs dynamically based on AUTH_USER_MODEL
|
21
|
+
user_admin_urls = get_user_admin_urls()
|
22
|
+
|
23
|
+
actions = [
|
24
|
+
QuickAction(
|
25
|
+
title="Add User",
|
26
|
+
description="Create new user account",
|
27
|
+
icon=Icons.PERSON_ADD,
|
28
|
+
link=user_admin_urls["add"],
|
29
|
+
color="primary",
|
30
|
+
category="admin",
|
31
|
+
),
|
32
|
+
QuickAction(
|
33
|
+
title="Support Tickets",
|
34
|
+
description="Manage support tickets",
|
35
|
+
icon=Icons.SUPPORT_AGENT,
|
36
|
+
link="admin:django_cfg_support_ticket_changelist",
|
37
|
+
color="primary",
|
38
|
+
category="support",
|
39
|
+
),
|
40
|
+
QuickAction(
|
41
|
+
title="Health Check",
|
42
|
+
description="System health status",
|
43
|
+
icon=Icons.HEALTH_AND_SAFETY,
|
44
|
+
link="/cfg/health/",
|
45
|
+
color="success",
|
46
|
+
category="system",
|
47
|
+
),
|
48
|
+
]
|
49
|
+
|
50
|
+
return actions
|
@@ -0,0 +1,98 @@
|
|
1
|
+
"""
|
2
|
+
Base utilities and helper functions for callbacks.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import logging
|
6
|
+
from typing import Dict, Any, List
|
7
|
+
from django.contrib.auth import get_user_model
|
8
|
+
from django.core.management import get_commands
|
9
|
+
import importlib
|
10
|
+
|
11
|
+
logger = logging.getLogger(__name__)
|
12
|
+
|
13
|
+
|
14
|
+
def get_available_commands():
|
15
|
+
"""Get all available Django management commands."""
|
16
|
+
commands_dict = get_commands()
|
17
|
+
commands_list = []
|
18
|
+
|
19
|
+
for command_name, app_name in commands_dict.items():
|
20
|
+
try:
|
21
|
+
# Try to get command description
|
22
|
+
if app_name == 'django_cfg':
|
23
|
+
module_path = f'django_cfg.management.commands.{command_name}'
|
24
|
+
else:
|
25
|
+
module_path = f'{app_name}.management.commands.{command_name}'
|
26
|
+
|
27
|
+
try:
|
28
|
+
command_module = importlib.import_module(module_path)
|
29
|
+
if hasattr(command_module, 'Command'):
|
30
|
+
command_class = command_module.Command
|
31
|
+
description = getattr(command_class, 'help', f'{command_name} command')
|
32
|
+
else:
|
33
|
+
description = f'{command_name} command'
|
34
|
+
except ImportError:
|
35
|
+
description = f'{command_name} command'
|
36
|
+
|
37
|
+
commands_list.append({
|
38
|
+
'name': command_name,
|
39
|
+
'app': app_name,
|
40
|
+
'description': description,
|
41
|
+
'is_core': app_name.startswith('django.'),
|
42
|
+
'is_custom': app_name == 'django_cfg',
|
43
|
+
})
|
44
|
+
except Exception:
|
45
|
+
# Skip problematic commands
|
46
|
+
continue
|
47
|
+
|
48
|
+
return commands_list
|
49
|
+
|
50
|
+
|
51
|
+
def get_commands_by_category():
|
52
|
+
"""Get commands categorized by type."""
|
53
|
+
commands = get_available_commands()
|
54
|
+
|
55
|
+
categorized = {
|
56
|
+
'django_cfg': [],
|
57
|
+
'django_core': [],
|
58
|
+
'third_party': [],
|
59
|
+
'project': [],
|
60
|
+
}
|
61
|
+
|
62
|
+
for cmd in commands:
|
63
|
+
if cmd['app'] == 'django_cfg':
|
64
|
+
categorized['django_cfg'].append(cmd)
|
65
|
+
elif cmd['app'].startswith('django.'):
|
66
|
+
categorized['django_core'].append(cmd)
|
67
|
+
elif cmd['app'].startswith(('src.', 'api.', 'accounts.')):
|
68
|
+
categorized['project'].append(cmd)
|
69
|
+
else:
|
70
|
+
categorized['third_party'].append(cmd)
|
71
|
+
|
72
|
+
return categorized
|
73
|
+
|
74
|
+
|
75
|
+
def get_user_admin_urls():
|
76
|
+
"""Get admin URLs for user model."""
|
77
|
+
try:
|
78
|
+
User = get_user_model()
|
79
|
+
|
80
|
+
app_label = User._meta.app_label
|
81
|
+
model_name = User._meta.model_name
|
82
|
+
|
83
|
+
return {
|
84
|
+
'changelist': f'admin:{app_label}_{model_name}_changelist',
|
85
|
+
'add': f'admin:{app_label}_{model_name}_add',
|
86
|
+
'change': f'admin:{app_label}_{model_name}_change/{{id}}/',
|
87
|
+
'delete': f'admin:{app_label}_{model_name}_delete/{{id}}/',
|
88
|
+
'view': f'admin:{app_label}_{model_name}_view/{{id}}/',
|
89
|
+
}
|
90
|
+
except Exception:
|
91
|
+
# Universal fallback - return admin index for all actions
|
92
|
+
return {
|
93
|
+
'changelist': 'admin:index',
|
94
|
+
'add': 'admin:index',
|
95
|
+
'change': 'admin:index',
|
96
|
+
'delete': 'admin:index',
|
97
|
+
'view': 'admin:index',
|
98
|
+
}
|
@@ -0,0 +1,224 @@
|
|
1
|
+
"""
|
2
|
+
Charts data callbacks.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import logging
|
6
|
+
import random
|
7
|
+
from typing import Dict, Any, List
|
8
|
+
from datetime import timedelta
|
9
|
+
|
10
|
+
from django.db.models import Count
|
11
|
+
from django.utils import timezone
|
12
|
+
from django.contrib.auth import get_user_model
|
13
|
+
|
14
|
+
from ..models.dashboard import ChartData, ChartDataset
|
15
|
+
|
16
|
+
logger = logging.getLogger(__name__)
|
17
|
+
|
18
|
+
|
19
|
+
class ChartsCallbacks:
|
20
|
+
"""Charts data callbacks."""
|
21
|
+
|
22
|
+
def _get_user_model(self):
|
23
|
+
"""Get the user model safely."""
|
24
|
+
return get_user_model()
|
25
|
+
|
26
|
+
def _get_empty_chart_data(self, label: str) -> Dict[str, Any]:
|
27
|
+
"""Get empty chart data structure."""
|
28
|
+
return ChartData(
|
29
|
+
labels=["No Data"],
|
30
|
+
datasets=[
|
31
|
+
ChartDataset(
|
32
|
+
label=label,
|
33
|
+
data=[0],
|
34
|
+
backgroundColor="rgba(156, 163, 175, 0.1)",
|
35
|
+
borderColor="rgb(156, 163, 175)",
|
36
|
+
tension=0.4
|
37
|
+
)
|
38
|
+
]
|
39
|
+
).model_dump()
|
40
|
+
|
41
|
+
def get_user_registration_chart_data(self) -> Dict[str, Any]:
|
42
|
+
"""Get user registration chart data."""
|
43
|
+
try:
|
44
|
+
# Avoid database access during app initialization
|
45
|
+
from django.apps import apps
|
46
|
+
if not apps.ready:
|
47
|
+
return self._get_empty_chart_data("New Users")
|
48
|
+
|
49
|
+
User = self._get_user_model()
|
50
|
+
|
51
|
+
# Get last 7 days of registration data
|
52
|
+
end_date = timezone.now().date()
|
53
|
+
start_date = end_date - timedelta(days=6)
|
54
|
+
|
55
|
+
# Generate date range
|
56
|
+
date_range = []
|
57
|
+
current_date = start_date
|
58
|
+
while current_date <= end_date:
|
59
|
+
date_range.append(current_date)
|
60
|
+
current_date += timedelta(days=1)
|
61
|
+
|
62
|
+
# Get registration counts by date
|
63
|
+
registration_data = (
|
64
|
+
User.objects.filter(date_joined__date__gte=start_date)
|
65
|
+
.extra({'date': "date(date_joined)"})
|
66
|
+
.values('date')
|
67
|
+
.annotate(count=Count('id'))
|
68
|
+
.order_by('date')
|
69
|
+
)
|
70
|
+
|
71
|
+
# Create data dictionary for easy lookup
|
72
|
+
data_dict = {item['date']: item['count'] for item in registration_data}
|
73
|
+
|
74
|
+
# Build chart data
|
75
|
+
labels = [date.strftime("%m/%d") for date in date_range]
|
76
|
+
data_points = [data_dict.get(date, 0) for date in date_range]
|
77
|
+
|
78
|
+
chart_data = ChartData(
|
79
|
+
labels=labels,
|
80
|
+
datasets=[
|
81
|
+
ChartDataset(
|
82
|
+
label="New Users",
|
83
|
+
data=data_points,
|
84
|
+
backgroundColor="rgba(59, 130, 246, 0.1)",
|
85
|
+
borderColor="rgb(59, 130, 246)",
|
86
|
+
tension=0.4
|
87
|
+
)
|
88
|
+
]
|
89
|
+
)
|
90
|
+
|
91
|
+
return chart_data.model_dump()
|
92
|
+
|
93
|
+
except Exception as e:
|
94
|
+
logger.error(f"Error getting user registration chart data: {e}")
|
95
|
+
return self._get_empty_chart_data("New Users")
|
96
|
+
|
97
|
+
def get_user_activity_chart_data(self) -> Dict[str, Any]:
|
98
|
+
"""Get user activity chart data."""
|
99
|
+
try:
|
100
|
+
# Avoid database access during app initialization
|
101
|
+
from django.apps import apps
|
102
|
+
if not apps.ready:
|
103
|
+
return self._get_empty_chart_data("Active Users")
|
104
|
+
|
105
|
+
User = self._get_user_model()
|
106
|
+
|
107
|
+
# Get activity data for last 7 days
|
108
|
+
end_date = timezone.now().date()
|
109
|
+
start_date = end_date - timedelta(days=6)
|
110
|
+
|
111
|
+
# Generate date range
|
112
|
+
date_range = []
|
113
|
+
current_date = start_date
|
114
|
+
while current_date <= end_date:
|
115
|
+
date_range.append(current_date)
|
116
|
+
current_date += timedelta(days=1)
|
117
|
+
|
118
|
+
# Get login activity (users who logged in each day)
|
119
|
+
activity_data = (
|
120
|
+
User.objects.filter(last_login__date__gte=start_date, last_login__isnull=False)
|
121
|
+
.extra({'date': "date(last_login)"})
|
122
|
+
.values('date')
|
123
|
+
.annotate(count=Count('id'))
|
124
|
+
.order_by('date')
|
125
|
+
)
|
126
|
+
|
127
|
+
# Create data dictionary for easy lookup
|
128
|
+
data_dict = {item['date']: item['count'] for item in activity_data}
|
129
|
+
|
130
|
+
# Build chart data
|
131
|
+
labels = [date.strftime("%m/%d") for date in date_range]
|
132
|
+
data_points = [data_dict.get(date, 0) for date in date_range]
|
133
|
+
|
134
|
+
chart_data = ChartData(
|
135
|
+
labels=labels,
|
136
|
+
datasets=[
|
137
|
+
ChartDataset(
|
138
|
+
label="Active Users",
|
139
|
+
data=data_points,
|
140
|
+
backgroundColor="rgba(34, 197, 94, 0.1)",
|
141
|
+
borderColor="rgb(34, 197, 94)",
|
142
|
+
tension=0.4
|
143
|
+
)
|
144
|
+
]
|
145
|
+
)
|
146
|
+
|
147
|
+
return chart_data.model_dump()
|
148
|
+
|
149
|
+
except Exception as e:
|
150
|
+
logger.error(f"Error getting user activity chart data: {e}")
|
151
|
+
return self._get_empty_chart_data("Active Users")
|
152
|
+
|
153
|
+
def get_activity_tracker_data(self) -> List[Dict[str, str]]:
|
154
|
+
"""Get activity tracker data for the last 52 weeks (GitHub-style)."""
|
155
|
+
try:
|
156
|
+
# Avoid database access during app initialization
|
157
|
+
from django.apps import apps
|
158
|
+
if not apps.ready:
|
159
|
+
return self._get_empty_tracker_data()
|
160
|
+
|
161
|
+
User = self._get_user_model()
|
162
|
+
|
163
|
+
# Get data for last 52 weeks (365 days)
|
164
|
+
end_date = timezone.now().date()
|
165
|
+
start_date = end_date - timedelta(days=364) # 52 weeks * 7 days - 1
|
166
|
+
|
167
|
+
# Get activity data by date
|
168
|
+
activity_data = (
|
169
|
+
User.objects.filter(last_login__date__gte=start_date, last_login__isnull=False)
|
170
|
+
.extra({'date': "date(last_login)"})
|
171
|
+
.values('date')
|
172
|
+
.annotate(count=Count('id'))
|
173
|
+
.order_by('date')
|
174
|
+
)
|
175
|
+
|
176
|
+
# Create data dictionary for easy lookup
|
177
|
+
data_dict = {item['date']: item['count'] for item in activity_data}
|
178
|
+
|
179
|
+
# Generate tracker data for each day
|
180
|
+
tracker_data = []
|
181
|
+
current_date = start_date
|
182
|
+
|
183
|
+
while current_date <= end_date:
|
184
|
+
activity_count = data_dict.get(current_date, 0)
|
185
|
+
|
186
|
+
# Determine color based on activity level
|
187
|
+
if activity_count == 0:
|
188
|
+
color = "bg-base-200 dark:bg-base-700"
|
189
|
+
level = "No activity"
|
190
|
+
elif activity_count <= 2:
|
191
|
+
color = "bg-green-200 dark:bg-green-800"
|
192
|
+
level = "Low activity"
|
193
|
+
elif activity_count <= 5:
|
194
|
+
color = "bg-green-400 dark:bg-green-600"
|
195
|
+
level = "Medium activity"
|
196
|
+
elif activity_count <= 10:
|
197
|
+
color = "bg-green-600 dark:bg-green-500"
|
198
|
+
level = "High activity"
|
199
|
+
else:
|
200
|
+
color = "bg-green-800 dark:bg-green-400"
|
201
|
+
level = "Very high activity"
|
202
|
+
|
203
|
+
tracker_data.append({
|
204
|
+
"color": color,
|
205
|
+
"tooltip": f"{current_date.strftime('%Y-%m-%d')}: {activity_count} active users ({level})"
|
206
|
+
})
|
207
|
+
|
208
|
+
current_date += timedelta(days=1)
|
209
|
+
|
210
|
+
return tracker_data
|
211
|
+
|
212
|
+
except Exception as e:
|
213
|
+
logger.error(f"Error getting activity tracker data: {e}")
|
214
|
+
return self._get_empty_tracker_data()
|
215
|
+
|
216
|
+
def _get_empty_tracker_data(self) -> List[Dict[str, str]]:
|
217
|
+
"""Get empty tracker data (365 days of no activity)."""
|
218
|
+
tracker_data = []
|
219
|
+
for i in range(365):
|
220
|
+
tracker_data.append({
|
221
|
+
"color": "bg-base-200 dark:bg-base-700",
|
222
|
+
"tooltip": f"Day {i + 1}: No data available"
|
223
|
+
})
|
224
|
+
return tracker_data
|