django-cfg 1.3.5__py3-none-any.whl → 1.3.9__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/accounts/admin/__init__.py +24 -8
- django_cfg/apps/accounts/admin/activity_admin.py +146 -0
- django_cfg/apps/accounts/admin/filters.py +98 -22
- django_cfg/apps/accounts/admin/group_admin.py +86 -0
- django_cfg/apps/accounts/admin/inlines.py +42 -13
- django_cfg/apps/accounts/admin/otp_admin.py +115 -0
- django_cfg/apps/accounts/admin/registration_admin.py +173 -0
- django_cfg/apps/accounts/admin/resources.py +123 -19
- django_cfg/apps/accounts/admin/twilio_admin.py +327 -0
- django_cfg/apps/accounts/admin/user_admin.py +362 -0
- django_cfg/apps/agents/admin/__init__.py +17 -4
- django_cfg/apps/agents/admin/execution_admin.py +204 -183
- django_cfg/apps/agents/admin/registry_admin.py +230 -255
- django_cfg/apps/agents/admin/toolsets_admin.py +274 -321
- django_cfg/apps/agents/core/__init__.py +1 -1
- django_cfg/apps/agents/core/django_agent.py +221 -0
- django_cfg/apps/agents/core/exceptions.py +14 -0
- django_cfg/apps/agents/core/orchestrator.py +18 -3
- django_cfg/apps/knowbase/admin/__init__.py +1 -1
- django_cfg/apps/knowbase/admin/archive_admin.py +352 -640
- django_cfg/apps/knowbase/admin/chat_admin.py +258 -192
- django_cfg/apps/knowbase/admin/document_admin.py +269 -262
- django_cfg/apps/knowbase/admin/external_data_admin.py +271 -489
- django_cfg/apps/knowbase/config/settings.py +21 -4
- django_cfg/apps/knowbase/views/chat_views.py +3 -0
- django_cfg/apps/leads/admin/__init__.py +3 -1
- django_cfg/apps/leads/admin/leads_admin.py +235 -35
- django_cfg/apps/maintenance/admin/__init__.py +2 -2
- django_cfg/apps/maintenance/admin/api_key_admin.py +125 -63
- django_cfg/apps/maintenance/admin/log_admin.py +143 -61
- django_cfg/apps/maintenance/admin/scheduled_admin.py +212 -301
- django_cfg/apps/maintenance/admin/site_admin.py +213 -352
- django_cfg/apps/newsletter/admin/__init__.py +29 -2
- django_cfg/apps/newsletter/admin/newsletter_admin.py +531 -193
- django_cfg/apps/payments/admin/__init__.py +18 -27
- django_cfg/apps/payments/admin/api_keys_admin.py +179 -546
- django_cfg/apps/payments/admin/balance_admin.py +166 -632
- django_cfg/apps/payments/admin/currencies_admin.py +235 -607
- django_cfg/apps/payments/admin/endpoint_groups_admin.py +127 -0
- django_cfg/apps/payments/admin/filters.py +83 -3
- django_cfg/apps/payments/admin/networks_admin.py +258 -0
- django_cfg/apps/payments/admin/payments_admin.py +171 -461
- django_cfg/apps/payments/admin/subscriptions_admin.py +119 -636
- django_cfg/apps/payments/admin/tariffs_admin.py +248 -0
- django_cfg/apps/payments/admin_interface/serializers/payment_serializers.py +105 -34
- django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +12 -16
- django_cfg/apps/payments/admin_interface/views/__init__.py +2 -0
- django_cfg/apps/payments/admin_interface/views/api/webhook_admin.py +13 -18
- django_cfg/apps/payments/management/commands/manage_currencies.py +236 -274
- django_cfg/apps/payments/management/commands/manage_providers.py +4 -1
- django_cfg/apps/payments/middleware/api_access.py +32 -6
- django_cfg/apps/payments/migrations/0002_currency_usd_rate_currency_usd_rate_updated_at.py +26 -0
- django_cfg/apps/payments/migrations/0003_remove_provider_currency_fields.py +28 -0
- django_cfg/apps/payments/migrations/0004_add_reserved_usd_field.py +30 -0
- django_cfg/apps/payments/models/balance.py +12 -0
- django_cfg/apps/payments/models/currencies.py +106 -32
- django_cfg/apps/payments/models/managers/currency_managers.py +65 -0
- django_cfg/apps/payments/services/core/currency_service.py +35 -28
- django_cfg/apps/payments/services/core/payment_service.py +1 -1
- django_cfg/apps/payments/services/providers/__init__.py +3 -0
- django_cfg/apps/payments/services/providers/base.py +95 -39
- django_cfg/apps/payments/services/providers/models/__init__.py +40 -0
- django_cfg/apps/payments/services/providers/models/base.py +122 -0
- django_cfg/apps/payments/services/providers/models/providers.py +87 -0
- django_cfg/apps/payments/services/providers/models/universal.py +48 -0
- django_cfg/apps/payments/services/providers/nowpayments/__init__.py +31 -0
- django_cfg/apps/payments/services/providers/nowpayments/config.py +70 -0
- django_cfg/apps/payments/services/providers/nowpayments/models.py +150 -0
- django_cfg/apps/payments/services/providers/nowpayments/parsers.py +879 -0
- django_cfg/apps/payments/services/providers/{nowpayments.py → nowpayments/provider.py} +240 -209
- django_cfg/apps/payments/services/providers/nowpayments/sync.py +196 -0
- django_cfg/apps/payments/services/providers/registry.py +4 -32
- django_cfg/apps/payments/services/providers/sync_service.py +277 -0
- django_cfg/apps/payments/static/payments/js/api-client.js +23 -5
- django_cfg/apps/payments/static/payments/js/payment-form.js +65 -8
- django_cfg/apps/payments/tasks/__init__.py +39 -0
- django_cfg/apps/payments/tasks/types.py +73 -0
- django_cfg/apps/payments/tasks/usage_tracking.py +308 -0
- django_cfg/apps/payments/templates/admin/payments/_components/dashboard_header.html +23 -0
- django_cfg/apps/payments/templates/admin/payments/_components/stats_card.html +25 -0
- django_cfg/apps/payments/templates/admin/payments/_components/stats_grid.html +16 -0
- django_cfg/apps/payments/templates/admin/payments/apikey/change_list.html +39 -0
- django_cfg/apps/payments/templates/admin/payments/balance/change_list.html +50 -0
- django_cfg/apps/payments/templates/admin/payments/currency/change_list.html +40 -0
- django_cfg/apps/payments/templates/admin/payments/payment/change_list.html +48 -0
- django_cfg/apps/payments/templates/admin/payments/subscription/change_list.html +48 -0
- django_cfg/apps/payments/urls_admin.py +1 -1
- django_cfg/apps/payments/views/api/currencies.py +5 -5
- django_cfg/apps/payments/views/overview/services.py +2 -2
- django_cfg/apps/payments/views/serializers/currencies.py +4 -3
- django_cfg/apps/support/admin/__init__.py +10 -1
- django_cfg/apps/support/admin/support_admin.py +338 -141
- django_cfg/apps/tasks/admin/__init__.py +11 -0
- django_cfg/apps/tasks/admin/tasks_admin.py +430 -0
- django_cfg/apps/urls.py +1 -2
- django_cfg/config.py +1 -1
- django_cfg/core/config.py +10 -5
- django_cfg/core/generation.py +1 -1
- django_cfg/management/commands/__init__.py +13 -1
- django_cfg/management/commands/app_agent_diagnose.py +470 -0
- django_cfg/management/commands/app_agent_generate.py +342 -0
- django_cfg/management/commands/app_agent_info.py +308 -0
- django_cfg/management/commands/migrate_all.py +9 -3
- django_cfg/management/commands/migrator.py +11 -6
- django_cfg/management/commands/rundramatiq.py +3 -2
- django_cfg/middleware/__init__.py +0 -2
- django_cfg/models/api_keys.py +115 -0
- django_cfg/modules/django_admin/__init__.py +64 -0
- django_cfg/modules/django_admin/decorators/__init__.py +13 -0
- django_cfg/modules/django_admin/decorators/actions.py +106 -0
- django_cfg/modules/django_admin/decorators/display.py +106 -0
- django_cfg/modules/django_admin/mixins/__init__.py +14 -0
- django_cfg/modules/django_admin/mixins/display_mixin.py +81 -0
- django_cfg/modules/django_admin/mixins/optimization_mixin.py +41 -0
- django_cfg/modules/django_admin/mixins/standalone_actions_mixin.py +202 -0
- django_cfg/modules/django_admin/models/__init__.py +20 -0
- django_cfg/modules/django_admin/models/action_models.py +33 -0
- django_cfg/modules/django_admin/models/badge_models.py +20 -0
- django_cfg/modules/django_admin/models/base.py +26 -0
- django_cfg/modules/django_admin/models/display_models.py +31 -0
- django_cfg/modules/django_admin/utils/badges.py +159 -0
- django_cfg/modules/django_admin/utils/displays.py +247 -0
- django_cfg/modules/django_app_agent/__init__.py +87 -0
- django_cfg/modules/django_app_agent/agents/__init__.py +40 -0
- django_cfg/modules/django_app_agent/agents/base/__init__.py +24 -0
- django_cfg/modules/django_app_agent/agents/base/agent.py +354 -0
- django_cfg/modules/django_app_agent/agents/base/context.py +236 -0
- django_cfg/modules/django_app_agent/agents/base/executor.py +430 -0
- django_cfg/modules/django_app_agent/agents/generation/__init__.py +12 -0
- django_cfg/modules/django_app_agent/agents/generation/app_generator/__init__.py +15 -0
- django_cfg/modules/django_app_agent/agents/generation/app_generator/config_validator.py +147 -0
- django_cfg/modules/django_app_agent/agents/generation/app_generator/main.py +99 -0
- django_cfg/modules/django_app_agent/agents/generation/app_generator/models.py +32 -0
- django_cfg/modules/django_app_agent/agents/generation/app_generator/prompt_manager.py +290 -0
- django_cfg/modules/django_app_agent/agents/interfaces.py +376 -0
- django_cfg/modules/django_app_agent/core/__init__.py +33 -0
- django_cfg/modules/django_app_agent/core/config.py +300 -0
- django_cfg/modules/django_app_agent/core/exceptions.py +359 -0
- django_cfg/modules/django_app_agent/models/__init__.py +71 -0
- django_cfg/modules/django_app_agent/models/base.py +283 -0
- django_cfg/modules/django_app_agent/models/context.py +496 -0
- django_cfg/modules/django_app_agent/models/enums.py +481 -0
- django_cfg/modules/django_app_agent/models/requests.py +500 -0
- django_cfg/modules/django_app_agent/models/responses.py +585 -0
- django_cfg/modules/django_app_agent/pytest.ini +6 -0
- django_cfg/modules/django_app_agent/services/__init__.py +42 -0
- django_cfg/modules/django_app_agent/services/app_generator/__init__.py +30 -0
- django_cfg/modules/django_app_agent/services/app_generator/ai_integration.py +133 -0
- django_cfg/modules/django_app_agent/services/app_generator/context.py +40 -0
- django_cfg/modules/django_app_agent/services/app_generator/main.py +202 -0
- django_cfg/modules/django_app_agent/services/app_generator/structure.py +316 -0
- django_cfg/modules/django_app_agent/services/app_generator/validation.py +125 -0
- django_cfg/modules/django_app_agent/services/base.py +437 -0
- django_cfg/modules/django_app_agent/services/context_builder/__init__.py +34 -0
- django_cfg/modules/django_app_agent/services/context_builder/code_extractor.py +141 -0
- django_cfg/modules/django_app_agent/services/context_builder/context_generator.py +276 -0
- django_cfg/modules/django_app_agent/services/context_builder/main.py +272 -0
- django_cfg/modules/django_app_agent/services/context_builder/models.py +40 -0
- django_cfg/modules/django_app_agent/services/context_builder/pattern_analyzer.py +85 -0
- django_cfg/modules/django_app_agent/services/project_scanner/__init__.py +31 -0
- django_cfg/modules/django_app_agent/services/project_scanner/app_discovery.py +311 -0
- django_cfg/modules/django_app_agent/services/project_scanner/main.py +221 -0
- django_cfg/modules/django_app_agent/services/project_scanner/models.py +59 -0
- django_cfg/modules/django_app_agent/services/project_scanner/pattern_detection.py +94 -0
- django_cfg/modules/django_app_agent/services/questioning_service/__init__.py +28 -0
- django_cfg/modules/django_app_agent/services/questioning_service/main.py +273 -0
- django_cfg/modules/django_app_agent/services/questioning_service/models.py +111 -0
- django_cfg/modules/django_app_agent/services/questioning_service/question_generator.py +251 -0
- django_cfg/modules/django_app_agent/services/questioning_service/response_processor.py +347 -0
- django_cfg/modules/django_app_agent/services/questioning_service/session_manager.py +356 -0
- django_cfg/modules/django_app_agent/services/report_service.py +332 -0
- django_cfg/modules/django_app_agent/services/template_manager/__init__.py +18 -0
- django_cfg/modules/django_app_agent/services/template_manager/jinja_engine.py +236 -0
- django_cfg/modules/django_app_agent/services/template_manager/main.py +159 -0
- django_cfg/modules/django_app_agent/services/template_manager/models.py +36 -0
- django_cfg/modules/django_app_agent/services/template_manager/template_loader.py +100 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/admin.py.j2 +105 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/apps.py.j2 +31 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/cfg_config.py.j2 +44 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/cfg_module.py.j2 +81 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/forms.py.j2 +107 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/models.py.j2 +139 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/serializers.py.j2 +91 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/tests.py.j2 +195 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/urls.py.j2 +35 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/views.py.j2 +211 -0
- django_cfg/modules/django_app_agent/services/template_manager/variable_processor.py +200 -0
- django_cfg/modules/django_app_agent/services/validation_service/__init__.py +25 -0
- django_cfg/modules/django_app_agent/services/validation_service/django_validator.py +333 -0
- django_cfg/modules/django_app_agent/services/validation_service/main.py +242 -0
- django_cfg/modules/django_app_agent/services/validation_service/models.py +66 -0
- django_cfg/modules/django_app_agent/services/validation_service/quality_validator.py +352 -0
- django_cfg/modules/django_app_agent/services/validation_service/security_validator.py +272 -0
- django_cfg/modules/django_app_agent/services/validation_service/syntax_validator.py +203 -0
- django_cfg/modules/django_app_agent/ui/__init__.py +25 -0
- django_cfg/modules/django_app_agent/ui/cli.py +419 -0
- django_cfg/modules/django_app_agent/ui/rich_components.py +622 -0
- django_cfg/modules/django_app_agent/utils/__init__.py +38 -0
- django_cfg/modules/django_app_agent/utils/logging.py +360 -0
- django_cfg/modules/django_app_agent/utils/validation.py +417 -0
- django_cfg/modules/django_currency/__init__.py +2 -2
- django_cfg/modules/django_currency/clients/__init__.py +2 -2
- django_cfg/modules/django_currency/clients/hybrid_client.py +587 -0
- django_cfg/modules/django_currency/core/converter.py +12 -12
- django_cfg/modules/django_currency/database/__init__.py +2 -2
- django_cfg/modules/django_currency/database/database_loader.py +93 -42
- django_cfg/modules/django_llm/llm/client.py +10 -2
- django_cfg/modules/django_unfold/callbacks/actions.py +1 -1
- django_cfg/modules/django_unfold/callbacks/statistics.py +1 -1
- django_cfg/modules/django_unfold/dashboard.py +14 -13
- django_cfg/modules/django_unfold/models/config.py +1 -1
- django_cfg/registry/core.py +3 -0
- django_cfg/registry/third_party.py +2 -2
- django_cfg/template_archive/django_sample.zip +0 -0
- {django_cfg-1.3.5.dist-info → django_cfg-1.3.9.dist-info}/METADATA +2 -1
- {django_cfg-1.3.5.dist-info → django_cfg-1.3.9.dist-info}/RECORD +224 -118
- django_cfg/apps/accounts/admin/activity.py +0 -96
- django_cfg/apps/accounts/admin/group.py +0 -17
- django_cfg/apps/accounts/admin/otp.py +0 -59
- django_cfg/apps/accounts/admin/registration_source.py +0 -97
- django_cfg/apps/accounts/admin/twilio_response.py +0 -227
- django_cfg/apps/accounts/admin/user.py +0 -300
- django_cfg/apps/agents/core/agent.py +0 -281
- django_cfg/apps/payments/admin_interface/old/payments/base.html +0 -175
- django_cfg/apps/payments/admin_interface/old/payments/components/dev_tool_card.html +0 -125
- django_cfg/apps/payments/admin_interface/old/payments/components/loading_spinner.html +0 -16
- django_cfg/apps/payments/admin_interface/old/payments/components/ngrok_status_card.html +0 -113
- django_cfg/apps/payments/admin_interface/old/payments/components/notification.html +0 -27
- django_cfg/apps/payments/admin_interface/old/payments/components/provider_card.html +0 -86
- django_cfg/apps/payments/admin_interface/old/payments/components/status_card.html +0 -35
- django_cfg/apps/payments/admin_interface/old/payments/currency_converter.html +0 -382
- django_cfg/apps/payments/admin_interface/old/payments/payment_dashboard.html +0 -309
- django_cfg/apps/payments/admin_interface/old/payments/payment_form.html +0 -303
- django_cfg/apps/payments/admin_interface/old/payments/payment_list.html +0 -382
- django_cfg/apps/payments/admin_interface/old/payments/payment_status.html +0 -500
- django_cfg/apps/payments/admin_interface/old/payments/webhook_dashboard.html +0 -518
- django_cfg/apps/payments/admin_interface/old/static/payments/css/components.css +0 -619
- django_cfg/apps/payments/admin_interface/old/static/payments/css/dashboard.css +0 -188
- django_cfg/apps/payments/admin_interface/old/static/payments/js/components.js +0 -545
- django_cfg/apps/payments/admin_interface/old/static/payments/js/ngrok-status.js +0 -163
- django_cfg/apps/payments/admin_interface/old/static/payments/js/utils.js +0 -412
- django_cfg/apps/tasks/admin.py +0 -320
- django_cfg/middleware/static_nocache.py +0 -55
- django_cfg/modules/django_currency/clients/yahoo_client.py +0 -157
- /django_cfg/modules/{django_unfold → django_admin}/icons/README.md +0 -0
- /django_cfg/modules/{django_unfold → django_admin}/icons/__init__.py +0 -0
- /django_cfg/modules/{django_unfold → django_admin}/icons/constants.py +0 -0
- /django_cfg/modules/{django_unfold → django_admin}/icons/generate_icons.py +0 -0
- {django_cfg-1.3.5.dist-info → django_cfg-1.3.9.dist-info}/WHEEL +0 -0
- {django_cfg-1.3.5.dist-info → django_cfg-1.3.9.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.3.5.dist-info → django_cfg-1.3.9.dist-info}/licenses/LICENSE +0 -0
@@ -1,22 +1,34 @@
|
|
1
1
|
"""
|
2
|
-
Document admin interfaces
|
2
|
+
Document admin interfaces using Django Admin Utilities.
|
3
|
+
|
4
|
+
Enhanced document management with Material Icons and optimized queries.
|
3
5
|
"""
|
4
6
|
|
5
|
-
from django.contrib import admin
|
6
|
-
from django.utils.html import format_html
|
7
|
+
from django.contrib import admin, messages
|
7
8
|
from django.urls import reverse
|
8
9
|
from django.utils.safestring import mark_safe
|
9
10
|
from django.db import models, IntegrityError
|
10
|
-
from django.db.models import Count, Sum, Avg
|
11
|
+
from django.db.models import Count, Sum, Avg, Q
|
11
12
|
from django.db.models.fields.json import JSONField
|
12
|
-
from django.contrib import messages
|
13
13
|
from django_json_widget.widgets import JSONEditorWidget
|
14
14
|
from unfold.admin import ModelAdmin, TabularInline
|
15
|
-
from unfold.decorators import display
|
16
15
|
from unfold.contrib.filters.admin import AutocompleteSelectFilter, AutocompleteSelectMultipleFilter
|
17
16
|
from unfold.contrib.forms.widgets import WysiwygWidget
|
18
17
|
from django_cfg import ImportExportModelAdmin, ExportMixin, ImportForm, ExportForm
|
19
18
|
|
19
|
+
from django_cfg.modules.django_admin import (
|
20
|
+
OptimizedModelAdmin,
|
21
|
+
DisplayMixin,
|
22
|
+
MoneyDisplayConfig,
|
23
|
+
StatusBadgeConfig,
|
24
|
+
DateTimeDisplayConfig,
|
25
|
+
Icons,
|
26
|
+
ActionVariant,
|
27
|
+
display,
|
28
|
+
action
|
29
|
+
)
|
30
|
+
from django_cfg.modules.django_admin.utils.badges import StatusBadge
|
31
|
+
|
20
32
|
from ..models import Document, DocumentChunk, DocumentCategory
|
21
33
|
|
22
34
|
|
@@ -27,20 +39,17 @@ class DocumentChunkInline(TabularInline):
|
|
27
39
|
verbose_name = "Document Chunk"
|
28
40
|
verbose_name_plural = "📄 Document Chunks (Read-only)"
|
29
41
|
extra = 0
|
30
|
-
max_num = 0
|
31
|
-
can_delete = False
|
32
|
-
show_change_link = True
|
42
|
+
max_num = 0
|
43
|
+
can_delete = False
|
44
|
+
show_change_link = True
|
33
45
|
|
34
46
|
def has_add_permission(self, request, obj=None):
|
35
|
-
"""Disable adding new chunks through inline."""
|
36
47
|
return False
|
37
48
|
|
38
49
|
def has_change_permission(self, request, obj=None):
|
39
|
-
"""Disable editing chunks through inline."""
|
40
50
|
return False
|
41
51
|
|
42
52
|
def has_delete_permission(self, request, obj=None):
|
43
|
-
"""Disable deleting chunks through inline."""
|
44
53
|
return False
|
45
54
|
|
46
55
|
fields = [
|
@@ -52,20 +61,15 @@ class DocumentChunkInline(TabularInline):
|
|
52
61
|
'has_embedding_inline', 'embedding_cost', 'created_at'
|
53
62
|
]
|
54
63
|
|
55
|
-
|
56
|
-
|
57
|
-
classes = ['collapse'] # Collapsed by default
|
64
|
+
hide_title = False
|
65
|
+
classes = ['collapse']
|
58
66
|
|
59
67
|
@display(description="Content Preview")
|
60
68
|
def content_preview_inline(self, obj):
|
61
69
|
"""Shortened content preview for inline display."""
|
62
70
|
if not obj.content:
|
63
|
-
return "
|
64
|
-
|
65
|
-
return format_html(
|
66
|
-
'<div style="max-width: 300px; font-size: 12px; color: #666;">{}</div>',
|
67
|
-
preview
|
68
|
-
)
|
71
|
+
return "—"
|
72
|
+
return obj.content[:100] + "..." if len(obj.content) > 100 else obj.content
|
69
73
|
|
70
74
|
@display(description="Has Embedding", boolean=True)
|
71
75
|
def has_embedding_inline(self, obj):
|
@@ -78,18 +82,23 @@ class DocumentChunkInline(TabularInline):
|
|
78
82
|
|
79
83
|
|
80
84
|
@admin.register(Document)
|
81
|
-
class DocumentAdmin(ModelAdmin, ImportExportModelAdmin):
|
82
|
-
"""Admin interface for Document model
|
85
|
+
class DocumentAdmin(OptimizedModelAdmin, DisplayMixin, ModelAdmin, ImportExportModelAdmin):
|
86
|
+
"""Admin interface for Document model using Django Admin Utilities."""
|
87
|
+
|
88
|
+
# Performance optimization
|
89
|
+
select_related_fields = ['user']
|
83
90
|
|
84
91
|
# Import/Export configuration
|
85
92
|
import_form_class = ImportForm
|
86
93
|
export_form_class = ExportForm
|
87
94
|
|
88
95
|
list_display = [
|
89
|
-
'title_display', 'categories_display', '
|
90
|
-
'
|
96
|
+
'title_display', 'categories_display', 'user_display',
|
97
|
+
'visibility_display', 'status_display', 'chunks_count_display',
|
98
|
+
'vectorization_progress', 'tokens_display', 'cost_display', 'created_at_display'
|
91
99
|
]
|
92
|
-
|
100
|
+
list_display_links = ['title_display']
|
101
|
+
ordering = ['-created_at']
|
93
102
|
inlines = [DocumentChunkInline]
|
94
103
|
list_filter = [
|
95
104
|
'processing_status', 'is_public', 'file_type', 'created_at',
|
@@ -106,29 +115,33 @@ class DocumentAdmin(ModelAdmin, ImportExportModelAdmin):
|
|
106
115
|
]
|
107
116
|
|
108
117
|
fieldsets = (
|
109
|
-
('Basic Information', {
|
110
|
-
'fields': ('id', 'title', 'user', 'categories', 'is_public', 'file_type', 'file_size')
|
118
|
+
('📄 Basic Information', {
|
119
|
+
'fields': ('id', 'title', 'user', 'categories', 'is_public', 'file_type', 'file_size'),
|
120
|
+
'classes': ('tab',)
|
111
121
|
}),
|
112
|
-
('Content', {
|
122
|
+
('📝 Content', {
|
113
123
|
'fields': ('content', 'content_hash', 'duplicate_check'),
|
124
|
+
'classes': ('tab',)
|
114
125
|
}),
|
115
|
-
('Processing Status', {
|
126
|
+
('⚙️ Processing Status', {
|
116
127
|
'fields': (
|
117
128
|
'processing_status', 'processing_started_at',
|
118
129
|
'processing_completed_at', 'processing_error'
|
119
|
-
)
|
130
|
+
),
|
131
|
+
'classes': ('tab',)
|
120
132
|
}),
|
121
|
-
('Statistics', {
|
122
|
-
'fields': ('chunks_count', 'total_tokens', 'total_cost_usd')
|
133
|
+
('📊 Statistics', {
|
134
|
+
'fields': ('chunks_count', 'total_tokens', 'total_cost_usd'),
|
135
|
+
'classes': ('tab',)
|
123
136
|
}),
|
124
|
-
('Metadata', {
|
137
|
+
('🔧 Metadata', {
|
125
138
|
'fields': ('metadata',),
|
126
|
-
'classes': ('collapse'
|
139
|
+
'classes': ('tab', 'collapse'),
|
127
140
|
'description': 'Auto-generated metadata (read-only)'
|
128
141
|
}),
|
129
|
-
('Timestamps', {
|
142
|
+
('⏰ Timestamps', {
|
130
143
|
'fields': ('created_at', 'updated_at'),
|
131
|
-
'classes': ('collapse'
|
144
|
+
'classes': ('tab', 'collapse')
|
132
145
|
})
|
133
146
|
)
|
134
147
|
filter_horizontal = ['categories']
|
@@ -139,20 +152,16 @@ class DocumentAdmin(ModelAdmin, ImportExportModelAdmin):
|
|
139
152
|
|
140
153
|
# Form field overrides
|
141
154
|
formfield_overrides = {
|
142
|
-
models.TextField: {
|
143
|
-
|
144
|
-
},
|
145
|
-
JSONField: {
|
146
|
-
"widget": JSONEditorWidget,
|
147
|
-
}
|
155
|
+
models.TextField: {"widget": WysiwygWidget},
|
156
|
+
JSONField: {"widget": JSONEditorWidget}
|
148
157
|
}
|
149
158
|
|
159
|
+
actions = ['reprocess_documents', 'mark_as_public', 'mark_as_private']
|
160
|
+
|
150
161
|
def get_queryset(self, request):
|
151
162
|
"""Optimize queryset with select_related and prefetch_related."""
|
152
|
-
# Use all_users() to show all documents in admin, then filter by user if needed
|
153
163
|
queryset = Document.objects.all_users().select_related('user').prefetch_related('categories')
|
154
164
|
|
155
|
-
# For non-superusers, filter by their own documents
|
156
165
|
if not request.user.is_superuser:
|
157
166
|
queryset = queryset.filter(user=request.user)
|
158
167
|
|
@@ -160,10 +169,9 @@ class DocumentAdmin(ModelAdmin, ImportExportModelAdmin):
|
|
160
169
|
|
161
170
|
def save_model(self, request, obj, form, change):
|
162
171
|
"""Automatically set user to current user when creating new documents."""
|
163
|
-
if not change:
|
172
|
+
if not change:
|
164
173
|
obj.user = request.user
|
165
174
|
|
166
|
-
# Check for duplicates using manager method
|
167
175
|
is_duplicate, existing_doc = Document.objects.check_duplicate_before_save(
|
168
176
|
user=obj.user,
|
169
177
|
content=obj.content
|
@@ -176,7 +184,6 @@ class DocumentAdmin(ModelAdmin, ImportExportModelAdmin):
|
|
176
184
|
f'(created {existing_doc.created_at.strftime("%Y-%m-%d %H:%M")}). '
|
177
185
|
f'Please modify the content or update the existing document.'
|
178
186
|
)
|
179
|
-
# Don't save, just return - this will keep the form open with the error
|
180
187
|
return
|
181
188
|
|
182
189
|
try:
|
@@ -190,8 +197,6 @@ class DocumentAdmin(ModelAdmin, ImportExportModelAdmin):
|
|
190
197
|
)
|
191
198
|
else:
|
192
199
|
messages.error(request, f'Database error: {str(e)}')
|
193
|
-
|
194
|
-
# Re-raise the exception to prevent saving
|
195
200
|
raise
|
196
201
|
|
197
202
|
@display(description="Document Title", ordering="title")
|
@@ -200,112 +205,71 @@ class DocumentAdmin(ModelAdmin, ImportExportModelAdmin):
|
|
200
205
|
title = obj.title or "Untitled Document"
|
201
206
|
if len(title) > 50:
|
202
207
|
title = title[:47] + "..."
|
203
|
-
|
204
|
-
|
205
|
-
|
208
|
+
|
209
|
+
config = StatusBadgeConfig(show_icons=True, icon=Icons.DESCRIPTION)
|
210
|
+
return StatusBadge.create(
|
211
|
+
text=title,
|
212
|
+
variant="primary",
|
213
|
+
config=config
|
206
214
|
)
|
207
215
|
|
208
|
-
@display(
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
)
|
216
|
-
def
|
217
|
-
"""Display visibility status
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
'none': 'info' # blue for no categories
|
243
|
-
}
|
244
|
-
)
|
216
|
+
@display(description="User")
|
217
|
+
def user_display(self, obj):
|
218
|
+
"""User display."""
|
219
|
+
if not obj.user:
|
220
|
+
return "—"
|
221
|
+
return self.display_user_simple(obj.user)
|
222
|
+
|
223
|
+
@display(description="Visibility")
|
224
|
+
def visibility_display(self, obj):
|
225
|
+
"""Display visibility status."""
|
226
|
+
if obj.is_public:
|
227
|
+
config = StatusBadgeConfig(show_icons=True, icon=Icons.PUBLIC)
|
228
|
+
return StatusBadge.create(text="Public", variant="success", config=config)
|
229
|
+
else:
|
230
|
+
config = StatusBadgeConfig(show_icons=True, icon=Icons.LOCK)
|
231
|
+
return StatusBadge.create(text="Private", variant="danger", config=config)
|
232
|
+
|
233
|
+
@display(description="Status")
|
234
|
+
def status_display(self, obj):
|
235
|
+
"""Display processing status."""
|
236
|
+
status_config = StatusBadgeConfig(
|
237
|
+
custom_mappings={
|
238
|
+
'pending': 'warning',
|
239
|
+
'processing': 'info',
|
240
|
+
'completed': 'success',
|
241
|
+
'failed': 'danger',
|
242
|
+
'cancelled': 'secondary'
|
243
|
+
},
|
244
|
+
show_icons=True,
|
245
|
+
icon=Icons.CHECK_CIRCLE if obj.processing_status == 'completed' else Icons.ERROR if obj.processing_status == 'failed' else Icons.SCHEDULE
|
246
|
+
)
|
247
|
+
return self.display_status_auto(obj, 'processing_status', status_config)
|
248
|
+
|
249
|
+
@display(description="Categories")
|
245
250
|
def categories_display(self, obj):
|
246
|
-
"""Display
|
251
|
+
"""Display categories count."""
|
247
252
|
categories = obj.categories.all()
|
248
253
|
|
249
254
|
if not categories:
|
250
|
-
return
|
255
|
+
return "No categories"
|
251
256
|
|
252
|
-
# Determine overall visibility status
|
253
257
|
public_count = sum(1 for cat in categories if cat.is_public)
|
254
258
|
private_count = len(categories) - public_count
|
255
259
|
|
256
260
|
if private_count == 0:
|
257
|
-
|
258
|
-
description = f"{len(categories)} public"
|
261
|
+
return f"{len(categories)} public"
|
259
262
|
elif public_count == 0:
|
260
|
-
|
261
|
-
description = f"{len(categories)} private"
|
263
|
+
return f"{len(categories)} private"
|
262
264
|
else:
|
263
|
-
|
264
|
-
description = f"{public_count} public, {private_count} private"
|
265
|
-
|
266
|
-
# Return tuple for label display: (status_key, display_text)
|
267
|
-
return status, f"{', '.join(cat.name for cat in categories)} ({description})"
|
265
|
+
return f"{public_count} public, {private_count} private"
|
268
266
|
|
269
|
-
@display(description="Category Details", dropdown=True)
|
270
|
-
def category_dropdown(self, obj):
|
271
|
-
"""Display category details in dropdown."""
|
272
|
-
categories = obj.categories.all()
|
273
|
-
|
274
|
-
if not categories:
|
275
|
-
return {
|
276
|
-
"title": "No Categories",
|
277
|
-
"content": "<p class='text-gray-500 p-4'>This document has no categories assigned.</p>"
|
278
|
-
}
|
279
|
-
|
280
|
-
# Build dropdown items for each category
|
281
|
-
items = []
|
282
|
-
for category in categories:
|
283
|
-
status_icon = "🟢" if category.is_public else "🔴"
|
284
|
-
visibility = "Public" if category.is_public else "Private"
|
285
|
-
|
286
|
-
items.append({
|
287
|
-
"title": f"{status_icon} {category.name}",
|
288
|
-
"link": f"/admin/knowbase/documentcategory/{category.id}/change/"
|
289
|
-
})
|
290
|
-
|
291
|
-
return {
|
292
|
-
"title": f"Categories ({len(categories)})",
|
293
|
-
"striped": True,
|
294
|
-
"height": 200,
|
295
|
-
"width": 300,
|
296
|
-
"items": items
|
297
|
-
}
|
298
|
-
|
299
267
|
@display(description="Chunks", ordering="chunks_count")
|
300
268
|
def chunks_count_display(self, obj):
|
301
|
-
"""Display chunks count
|
269
|
+
"""Display chunks count."""
|
302
270
|
count = obj.chunks_count
|
303
271
|
if count > 0:
|
304
|
-
|
305
|
-
return format_html(
|
306
|
-
'<a href="{}" style="text-decoration: none;">{} chunks</a>',
|
307
|
-
url, count
|
308
|
-
)
|
272
|
+
return f"{count} chunks"
|
309
273
|
return "0 chunks"
|
310
274
|
|
311
275
|
@display(description="Tokens", ordering="total_tokens")
|
@@ -319,21 +283,24 @@ class DocumentAdmin(ModelAdmin, ImportExportModelAdmin):
|
|
319
283
|
@display(description="Cost (USD)", ordering="total_cost_usd")
|
320
284
|
def cost_display(self, obj):
|
321
285
|
"""Display cost with currency formatting."""
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
'no_chunks': 'info' # blue for no chunks
|
331
|
-
}
|
332
|
-
)
|
286
|
+
config = MoneyDisplayConfig(
|
287
|
+
currency="USD",
|
288
|
+
decimal_places=6,
|
289
|
+
show_sign=False
|
290
|
+
)
|
291
|
+
return self.display_money_amount(obj, 'total_cost_usd', config)
|
292
|
+
|
293
|
+
@display(description="Vectorization")
|
333
294
|
def vectorization_progress(self, obj):
|
334
|
-
"""Display vectorization progress
|
295
|
+
"""Display vectorization progress."""
|
335
296
|
return Document.objects.get_vectorization_status_display(obj)
|
336
297
|
|
298
|
+
@display(description="Created")
|
299
|
+
def created_at_display(self, obj):
|
300
|
+
"""Created time with relative display."""
|
301
|
+
config = DateTimeDisplayConfig(show_relative=True)
|
302
|
+
return self.display_datetime_relative(obj, 'created_at', config)
|
303
|
+
|
337
304
|
@display(description="Processing Duration")
|
338
305
|
def processing_duration_display(self, obj):
|
339
306
|
"""Display processing duration in readable format."""
|
@@ -357,37 +324,41 @@ class DocumentAdmin(ModelAdmin, ImportExportModelAdmin):
|
|
357
324
|
|
358
325
|
if isinstance(duplicate_info, str):
|
359
326
|
if "No duplicates found" in duplicate_info:
|
360
|
-
return
|
361
|
-
|
362
|
-
)
|
363
|
-
return duplicate_info # "No content hash"
|
327
|
+
return "✓ No duplicates found"
|
328
|
+
return duplicate_info
|
364
329
|
|
365
|
-
# Format the duplicate information for display
|
366
330
|
duplicates_data = duplicate_info['duplicates']
|
367
331
|
count = duplicate_info['count']
|
368
332
|
|
369
|
-
|
370
|
-
|
371
|
-
url = reverse('admin:django_cfg_knowbase_document_change', args=[dup.pk])
|
372
|
-
duplicate_list.append(
|
373
|
-
f'<a href="{url}" target="_blank">{dup.title}</a> '
|
374
|
-
f'({dup.created_at.strftime("%Y-%m-%d")})'
|
375
|
-
)
|
376
|
-
|
377
|
-
warning_text = f"⚠️ Found {count} duplicate(s):<br>" + "<br>".join(duplicate_list)
|
333
|
+
duplicate_names = [dup.title for dup in duplicates_data[:3]]
|
334
|
+
result = f"⚠️ Found {count} duplicate(s): " + ", ".join(duplicate_names)
|
378
335
|
if count > 3:
|
379
|
-
|
336
|
+
result += f" and {count - 3} more"
|
380
337
|
|
381
|
-
return
|
382
|
-
|
383
|
-
|
384
|
-
|
338
|
+
return result
|
339
|
+
|
340
|
+
@action(description="Reprocess documents", variant=ActionVariant.INFO)
|
341
|
+
def reprocess_documents(self, request, queryset):
|
342
|
+
"""Reprocess selected documents."""
|
343
|
+
count = queryset.count()
|
344
|
+
messages.info(request, f"Reprocessing functionality not implemented yet. {count} documents selected.")
|
345
|
+
|
346
|
+
@action(description="Mark as public", variant=ActionVariant.SUCCESS)
|
347
|
+
def mark_as_public(self, request, queryset):
|
348
|
+
"""Mark selected documents as public."""
|
349
|
+
updated = queryset.update(is_public=True)
|
350
|
+
messages.success(request, f"Marked {updated} documents as public.")
|
351
|
+
|
352
|
+
@action(description="Mark as private", variant=ActionVariant.WARNING)
|
353
|
+
def mark_as_private(self, request, queryset):
|
354
|
+
"""Mark selected documents as private."""
|
355
|
+
updated = queryset.update(is_public=False)
|
356
|
+
messages.warning(request, f"Marked {updated} documents as private.")
|
385
357
|
|
386
358
|
def changelist_view(self, request, extra_context=None):
|
387
359
|
"""Add summary statistics to changelist."""
|
388
360
|
extra_context = extra_context or {}
|
389
361
|
|
390
|
-
# Get summary statistics
|
391
362
|
queryset = self.get_queryset(request)
|
392
363
|
stats = queryset.aggregate(
|
393
364
|
total_documents=Count('id'),
|
@@ -396,7 +367,6 @@ class DocumentAdmin(ModelAdmin, ImportExportModelAdmin):
|
|
396
367
|
total_cost=Sum('total_cost_usd')
|
397
368
|
)
|
398
369
|
|
399
|
-
# Status breakdown
|
400
370
|
status_counts = dict(
|
401
371
|
queryset.values_list('processing_status').annotate(
|
402
372
|
count=Count('id')
|
@@ -415,14 +385,18 @@ class DocumentAdmin(ModelAdmin, ImportExportModelAdmin):
|
|
415
385
|
|
416
386
|
|
417
387
|
@admin.register(DocumentChunk)
|
418
|
-
class DocumentChunkAdmin(ModelAdmin):
|
419
|
-
"""Admin interface for DocumentChunk model
|
388
|
+
class DocumentChunkAdmin(OptimizedModelAdmin, DisplayMixin, ModelAdmin):
|
389
|
+
"""Admin interface for DocumentChunk model using Django Admin Utilities."""
|
390
|
+
|
391
|
+
# Performance optimization
|
392
|
+
select_related_fields = ['document', 'user']
|
420
393
|
|
421
394
|
list_display = [
|
422
|
-
'chunk_display', '
|
423
|
-
'embedding_status', 'embedding_cost_display', '
|
395
|
+
'chunk_display', 'document_display', 'user_display', 'token_count_display',
|
396
|
+
'embedding_status', 'embedding_cost_display', 'created_at_display'
|
424
397
|
]
|
425
|
-
|
398
|
+
list_display_links = ['chunk_display']
|
399
|
+
ordering = ['-created_at']
|
426
400
|
list_filter = [
|
427
401
|
'embedding_model', 'created_at',
|
428
402
|
('user', AutocompleteSelectFilter),
|
@@ -435,27 +409,30 @@ class DocumentChunkAdmin(ModelAdmin):
|
|
435
409
|
]
|
436
410
|
|
437
411
|
fieldsets = (
|
438
|
-
('Basic Information', {
|
439
|
-
'fields': ('id', 'document', 'user', 'chunk_index')
|
412
|
+
('📄 Basic Information', {
|
413
|
+
'fields': ('id', 'document', 'user', 'chunk_index'),
|
414
|
+
'classes': ('tab',)
|
440
415
|
}),
|
441
|
-
('Content', {
|
442
|
-
'fields': ('content_preview', 'content')
|
416
|
+
('📝 Content', {
|
417
|
+
'fields': ('content_preview', 'content'),
|
418
|
+
'classes': ('tab',)
|
443
419
|
}),
|
444
|
-
('Embedding Information', {
|
420
|
+
('🔗 Embedding Information', {
|
445
421
|
'fields': ('embedding_model', 'token_count', 'character_count', 'embedding_cost'),
|
422
|
+
'classes': ('tab',)
|
446
423
|
}),
|
447
|
-
('Vector Embedding', {
|
424
|
+
('🧠 Vector Embedding', {
|
448
425
|
'fields': ('embedding',),
|
449
|
-
'classes': ('collapse'
|
426
|
+
'classes': ('tab', 'collapse')
|
450
427
|
}),
|
451
|
-
('Metadata', {
|
428
|
+
('🔧 Metadata', {
|
452
429
|
'fields': ('metadata',),
|
453
|
-
'classes': ('collapse'
|
430
|
+
'classes': ('tab', 'collapse'),
|
454
431
|
'description': 'Auto-generated chunk metadata (read-only)'
|
455
432
|
}),
|
456
|
-
('Timestamps', {
|
433
|
+
('⏰ Timestamps', {
|
457
434
|
'fields': ('created_at', 'updated_at'),
|
458
|
-
'classes': ('collapse'
|
435
|
+
'classes': ('tab', 'collapse')
|
459
436
|
})
|
460
437
|
)
|
461
438
|
|
@@ -465,19 +442,32 @@ class DocumentChunkAdmin(ModelAdmin):
|
|
465
442
|
|
466
443
|
# Form field overrides
|
467
444
|
formfield_overrides = {
|
468
|
-
JSONField: {
|
469
|
-
"widget": JSONEditorWidget,
|
470
|
-
}
|
445
|
+
JSONField: {"widget": JSONEditorWidget}
|
471
446
|
}
|
472
447
|
|
473
|
-
|
474
|
-
"""Optimize queryset with select_related."""
|
475
|
-
return super().get_queryset(request).select_related('document', 'user')
|
448
|
+
actions = ['regenerate_embeddings', 'clear_embeddings']
|
476
449
|
|
477
450
|
@display(description="Chunk", ordering="chunk_index")
|
478
451
|
def chunk_display(self, obj):
|
479
452
|
"""Display chunk identifier."""
|
480
|
-
|
453
|
+
config = StatusBadgeConfig(show_icons=True, icon=Icons.ARTICLE)
|
454
|
+
return StatusBadge.create(
|
455
|
+
text=f"Chunk {obj.chunk_index + 1}",
|
456
|
+
variant="info",
|
457
|
+
config=config
|
458
|
+
)
|
459
|
+
|
460
|
+
@display(description="Document", ordering="document__title")
|
461
|
+
def document_display(self, obj):
|
462
|
+
"""Display document title."""
|
463
|
+
return obj.document.title
|
464
|
+
|
465
|
+
@display(description="User")
|
466
|
+
def user_display(self, obj):
|
467
|
+
"""User display."""
|
468
|
+
if not obj.user:
|
469
|
+
return "—"
|
470
|
+
return self.display_user_simple(obj.user)
|
481
471
|
|
482
472
|
@display(description="Tokens", ordering="token_count")
|
483
473
|
def token_count_display(self, obj):
|
@@ -487,58 +477,56 @@ class DocumentChunkAdmin(ModelAdmin):
|
|
487
477
|
return f"{tokens/1000:.1f}K"
|
488
478
|
return str(tokens)
|
489
479
|
|
490
|
-
@display(
|
491
|
-
description="Embedding",
|
492
|
-
label={
|
493
|
-
True: 'success', # green for has embedding
|
494
|
-
False: 'danger' # red for no embedding
|
495
|
-
}
|
496
|
-
)
|
480
|
+
@display(description="Embedding")
|
497
481
|
def embedding_status(self, obj):
|
498
482
|
"""Display embedding status."""
|
499
483
|
has_embedding = obj.embedding is not None and len(obj.embedding) > 0
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
return format_html(
|
507
|
-
'<a href="{}" style="text-decoration: none;">{}</a>',
|
508
|
-
url,
|
509
|
-
obj.document.title
|
510
|
-
)
|
484
|
+
if has_embedding:
|
485
|
+
config = StatusBadgeConfig(show_icons=True, icon=Icons.CHECK_CIRCLE)
|
486
|
+
return StatusBadge.create(text="✓ Vectorized", variant="success", config=config)
|
487
|
+
else:
|
488
|
+
config = StatusBadgeConfig(show_icons=True, icon=Icons.ERROR)
|
489
|
+
return StatusBadge.create(text="✗ Not vectorized", variant="danger", config=config)
|
511
490
|
|
512
491
|
@display(description="Cost (USD)", ordering="embedding_cost")
|
513
492
|
def embedding_cost_display(self, obj):
|
514
493
|
"""Display embedding cost with currency formatting."""
|
515
|
-
|
494
|
+
config = MoneyDisplayConfig(
|
495
|
+
currency="USD",
|
496
|
+
decimal_places=6,
|
497
|
+
show_sign=False
|
498
|
+
)
|
499
|
+
return self.display_money_amount(obj, 'embedding_cost', config)
|
500
|
+
|
501
|
+
@display(description="Created")
|
502
|
+
def created_at_display(self, obj):
|
503
|
+
"""Created time with relative display."""
|
504
|
+
config = DateTimeDisplayConfig(show_relative=True)
|
505
|
+
return self.display_datetime_relative(obj, 'created_at', config)
|
516
506
|
|
517
507
|
@display(description="Content Preview")
|
518
508
|
def content_preview(self, obj):
|
519
509
|
"""Display content preview with truncation."""
|
520
|
-
|
521
|
-
return format_html(
|
522
|
-
'<div style="max-width: 400px; word-wrap: break-word;">{}</div>',
|
523
|
-
preview
|
524
|
-
)
|
525
|
-
|
526
|
-
@display(description="Has Embedding", boolean=True)
|
527
|
-
def has_embedding(self, obj):
|
528
|
-
"""Check if chunk has embedding vector."""
|
529
|
-
return obj.embedding is not None and len(obj.embedding) > 0
|
510
|
+
return obj.content[:200] + "..." if len(obj.content) > 200 else obj.content
|
530
511
|
|
531
512
|
@display(description="Embedding Info")
|
532
513
|
def embedding_info(self, obj):
|
533
514
|
"""Display embedding information safely."""
|
534
515
|
if obj.embedding is not None and len(obj.embedding) > 0:
|
535
|
-
return
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
)
|
516
|
+
return f"✓ Vector ({len(obj.embedding)} dimensions)"
|
517
|
+
return "✗ No embedding"
|
518
|
+
|
519
|
+
@action(description="Regenerate embeddings", variant=ActionVariant.INFO)
|
520
|
+
def regenerate_embeddings(self, request, queryset):
|
521
|
+
"""Regenerate embeddings for selected chunks."""
|
522
|
+
count = queryset.count()
|
523
|
+
messages.info(request, f"Regenerate embeddings functionality not implemented yet. {count} chunks selected.")
|
524
|
+
|
525
|
+
@action(description="Clear embeddings", variant=ActionVariant.WARNING)
|
526
|
+
def clear_embeddings(self, request, queryset):
|
527
|
+
"""Clear embeddings for selected chunks."""
|
528
|
+
updated = queryset.update(embedding=None)
|
529
|
+
messages.warning(request, f"Cleared embeddings for {updated} chunks.")
|
542
530
|
|
543
531
|
def changelist_view(self, request, extra_context=None):
|
544
532
|
"""Add chunk statistics to changelist."""
|
@@ -553,7 +541,6 @@ class DocumentChunkAdmin(ModelAdmin):
|
|
553
541
|
avg_tokens_per_chunk=Avg('token_count')
|
554
542
|
)
|
555
543
|
|
556
|
-
# Model breakdown
|
557
544
|
model_counts = dict(
|
558
545
|
queryset.values_list('embedding_model').annotate(
|
559
546
|
count=Count('id')
|
@@ -573,28 +560,30 @@ class DocumentChunkAdmin(ModelAdmin):
|
|
573
560
|
|
574
561
|
|
575
562
|
@admin.register(DocumentCategory)
|
576
|
-
class DocumentCategoryAdmin(ModelAdmin, ImportExportModelAdmin):
|
577
|
-
"""Admin interface for DocumentCategory model
|
563
|
+
class DocumentCategoryAdmin(OptimizedModelAdmin, DisplayMixin, ModelAdmin, ImportExportModelAdmin):
|
564
|
+
"""Admin interface for DocumentCategory model using Django Admin Utilities."""
|
578
565
|
|
579
566
|
# Import/Export configuration
|
580
567
|
import_form_class = ImportForm
|
581
568
|
export_form_class = ExportForm
|
582
569
|
|
583
570
|
list_display = [
|
584
|
-
'short_uuid', '
|
571
|
+
'short_uuid', 'name_display', 'visibility_display', 'document_count', 'created_at_display'
|
585
572
|
]
|
586
|
-
|
573
|
+
list_display_links = ['name_display']
|
574
|
+
ordering = ['-created_at']
|
587
575
|
list_filter = ['is_public', 'created_at']
|
588
576
|
search_fields = ['name', 'description']
|
589
577
|
readonly_fields = ['id', 'created_at', 'updated_at']
|
590
578
|
|
591
579
|
fieldsets = (
|
592
|
-
('Basic Information', {
|
593
|
-
'fields': ('id', 'name', 'description', 'is_public')
|
580
|
+
('📁 Basic Information', {
|
581
|
+
'fields': ('id', 'name', 'description', 'is_public'),
|
582
|
+
'classes': ('tab',)
|
594
583
|
}),
|
595
|
-
('Timestamps', {
|
584
|
+
('⏰ Timestamps', {
|
596
585
|
'fields': ('created_at', 'updated_at'),
|
597
|
-
'classes': ('collapse'
|
586
|
+
'classes': ('tab', 'collapse')
|
598
587
|
})
|
599
588
|
)
|
600
589
|
|
@@ -604,36 +593,54 @@ class DocumentCategoryAdmin(ModelAdmin, ImportExportModelAdmin):
|
|
604
593
|
|
605
594
|
# Form field overrides
|
606
595
|
formfield_overrides = {
|
607
|
-
models.TextField: {
|
608
|
-
"widget": WysiwygWidget,
|
609
|
-
}
|
596
|
+
models.TextField: {"widget": WysiwygWidget}
|
610
597
|
}
|
611
598
|
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
599
|
+
actions = ['make_public', 'make_private']
|
600
|
+
|
601
|
+
@display(description="Category Name")
|
602
|
+
def name_display(self, obj):
|
603
|
+
"""Display category name."""
|
604
|
+
config = StatusBadgeConfig(show_icons=True, icon=Icons.FOLDER)
|
605
|
+
return StatusBadge.create(
|
606
|
+
text=obj.name,
|
607
|
+
variant="primary",
|
608
|
+
config=config
|
609
|
+
)
|
610
|
+
|
611
|
+
@display(description="Visibility")
|
612
|
+
def visibility_display(self, obj):
|
613
|
+
"""Display visibility status."""
|
614
|
+
if obj.is_public:
|
615
|
+
config = StatusBadgeConfig(show_icons=True, icon=Icons.PUBLIC)
|
616
|
+
return StatusBadge.create(text="Public", variant="success", config=config)
|
617
|
+
else:
|
618
|
+
config = StatusBadgeConfig(show_icons=True, icon=Icons.LOCK)
|
619
|
+
return StatusBadge.create(text="Private", variant="danger", config=config)
|
625
620
|
|
626
621
|
@display(description="Documents", ordering="document_count")
|
627
622
|
def document_count(self, obj):
|
628
623
|
"""Display count of documents in this category."""
|
629
624
|
count = obj.documents.count()
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
return
|
625
|
+
return f"{count} documents"
|
626
|
+
|
627
|
+
@display(description="Created")
|
628
|
+
def created_at_display(self, obj):
|
629
|
+
"""Created time with relative display."""
|
630
|
+
config = DateTimeDisplayConfig(show_relative=True)
|
631
|
+
return self.display_datetime_relative(obj, 'created_at', config)
|
632
|
+
|
633
|
+
@action(description="Make public", variant=ActionVariant.SUCCESS)
|
634
|
+
def make_public(self, request, queryset):
|
635
|
+
"""Make selected categories public."""
|
636
|
+
updated = queryset.update(is_public=True)
|
637
|
+
messages.success(request, f"Made {updated} categories public.")
|
638
|
+
|
639
|
+
@action(description="Make private", variant=ActionVariant.WARNING)
|
640
|
+
def make_private(self, request, queryset):
|
641
|
+
"""Make selected categories private."""
|
642
|
+
updated = queryset.update(is_public=False)
|
643
|
+
messages.warning(request, f"Made {updated} categories private.")
|
637
644
|
|
638
645
|
def get_queryset(self, request):
|
639
646
|
"""Optimize queryset with prefetch_related."""
|