django-cfg 1.3.7__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/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.7.dist-info → django_cfg-1.3.9.dist-info}/METADATA +2 -1
- {django_cfg-1.3.7.dist-info → django_cfg-1.3.9.dist-info}/RECORD +223 -117
- 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.7.dist-info → django_cfg-1.3.9.dist-info}/WHEEL +0 -0
- {django_cfg-1.3.7.dist-info → django_cfg-1.3.9.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.3.7.dist-info → django_cfg-1.3.9.dist-info}/licenses/LICENSE +0 -0
@@ -1,26 +1,44 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
1
|
+
"""
|
2
|
+
Support admin interfaces using Django Admin Utilities.
|
3
|
+
|
4
|
+
Enhanced support ticket management with Material Icons and optimized queries.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from django.contrib import admin, messages
|
6
8
|
from django.urls import reverse
|
7
9
|
from django.shortcuts import redirect
|
8
10
|
from django.http import HttpRequest
|
9
11
|
from django.utils.translation import gettext_lazy as _
|
12
|
+
from django.utils.timesince import timesince
|
13
|
+
from django.db import models
|
14
|
+
from django.db.models import Count, Q
|
15
|
+
from unfold.admin import ModelAdmin, TabularInline
|
10
16
|
from django_cfg import ExportMixin, ExportForm
|
11
17
|
|
18
|
+
from django_cfg.modules.django_admin import (
|
19
|
+
OptimizedModelAdmin,
|
20
|
+
DisplayMixin,
|
21
|
+
StatusBadgeConfig,
|
22
|
+
DateTimeDisplayConfig,
|
23
|
+
Icons,
|
24
|
+
ActionVariant,
|
25
|
+
display,
|
26
|
+
action
|
27
|
+
)
|
28
|
+
from django_cfg.modules.django_admin.utils.badges import StatusBadge
|
29
|
+
|
12
30
|
from ..models import Ticket, Message
|
13
31
|
from .filters import TicketUserEmailFilter, TicketUserNameFilter, MessageSenderEmailFilter
|
14
32
|
from .resources import TicketResource, MessageResource
|
15
|
-
|
33
|
+
|
16
34
|
|
17
35
|
class MessageInline(TabularInline):
|
18
36
|
"""Read-only inline for viewing messages. Use Chat interface for replies."""
|
19
37
|
|
20
38
|
model = Message
|
21
39
|
extra = 0
|
22
|
-
fields =
|
23
|
-
readonly_fields =
|
40
|
+
fields = ["sender_display", "created_at", "text_preview"]
|
41
|
+
readonly_fields = ["sender_display", "created_at", "text_preview"]
|
24
42
|
show_change_link = False
|
25
43
|
classes = ('collapse',)
|
26
44
|
|
@@ -32,174 +50,353 @@ class MessageInline(TabularInline):
|
|
32
50
|
"""Disable deleting messages through admin."""
|
33
51
|
return False
|
34
52
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
53
|
+
@display(description="Sender")
|
54
|
+
def sender_display(self, obj):
|
55
|
+
"""Display sender with badge."""
|
56
|
+
if not obj.sender:
|
57
|
+
return "—"
|
58
|
+
|
59
|
+
# Determine sender type and variant
|
60
|
+
if obj.sender.is_superuser:
|
61
|
+
variant = "danger"
|
62
|
+
icon = Icons.ADMIN_PANEL_SETTINGS
|
63
|
+
elif obj.sender.is_staff:
|
64
|
+
variant = "primary"
|
65
|
+
icon = Icons.SUPPORT_AGENT
|
42
66
|
else:
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
67
|
+
variant = "info"
|
68
|
+
icon = Icons.PERSON
|
69
|
+
|
70
|
+
config = StatusBadgeConfig(show_icons=True, icon=icon)
|
71
|
+
return StatusBadge.create(
|
72
|
+
text=obj.sender.get_full_name() or obj.sender.username,
|
73
|
+
variant=variant,
|
74
|
+
config=config
|
75
|
+
)
|
76
|
+
|
77
|
+
@display(description="Message")
|
78
|
+
def text_preview(self, obj):
|
79
|
+
"""Display message preview."""
|
80
|
+
if not obj.text:
|
81
|
+
return "—"
|
82
|
+
|
83
|
+
preview = obj.text[:100]
|
84
|
+
if len(obj.text) > 100:
|
85
|
+
preview += "..."
|
86
|
+
|
87
|
+
return preview
|
53
88
|
|
54
89
|
|
55
90
|
@admin.register(Ticket)
|
56
|
-
class TicketAdmin(ModelAdmin, ExportMixin):
|
91
|
+
class TicketAdmin(OptimizedModelAdmin, DisplayMixin, ModelAdmin, ExportMixin):
|
92
|
+
"""Admin interface for Ticket using Django Admin Utilities."""
|
93
|
+
|
94
|
+
# Performance optimization
|
95
|
+
select_related_fields = ['user']
|
96
|
+
|
57
97
|
# Export-only configuration
|
58
98
|
resource_class = TicketResource
|
59
99
|
export_form_class = ExportForm
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
100
|
+
|
101
|
+
list_display = [
|
102
|
+
"user_display", "uuid_display", "subject_display", "status_display",
|
103
|
+
"last_message_display", "last_message_ago_display", "chat_link_display", "created_at_display"
|
104
|
+
]
|
105
|
+
list_display_links = ["subject_display"]
|
106
|
+
ordering = ["-created_at"]
|
107
|
+
search_fields = ["uuid", "user__username", "user__email", "subject"]
|
108
|
+
list_filter = ["status", "created_at", TicketUserEmailFilter, TicketUserNameFilter]
|
66
109
|
inlines = [MessageInline]
|
110
|
+
autocomplete_fields = ["user"]
|
111
|
+
|
112
|
+
actions = ["mark_as_open", "mark_as_waiting_for_user", "mark_as_waiting_for_admin", "mark_as_resolved", "mark_as_closed"]
|
113
|
+
|
67
114
|
def get_readonly_fields(self, request, obj=None):
|
68
115
|
"""Different readonly fields for add/change forms."""
|
69
116
|
if obj is None: # Adding new ticket
|
70
117
|
return ("uuid", "created_at")
|
71
118
|
else: # Editing existing ticket
|
72
119
|
return ("uuid", "user", "created_at")
|
73
|
-
|
74
|
-
autocomplete_fields = ["user"]
|
120
|
+
|
75
121
|
def get_fieldsets(self, request, obj=None):
|
76
122
|
"""Different fieldsets for add/change forms."""
|
77
123
|
if obj is None: # Adding new ticket
|
78
124
|
return (
|
79
|
-
(
|
80
|
-
"fields": ("user", "subject", "status")
|
125
|
+
('🎫 New Ticket', {
|
126
|
+
"fields": ("user", "subject", "status"),
|
127
|
+
"classes": ("tab",)
|
81
128
|
}),
|
82
129
|
)
|
83
130
|
else: # Editing existing ticket
|
84
131
|
return (
|
85
|
-
(
|
86
|
-
"fields": (("uuid", "user"), "subject", "status", "created_at")
|
132
|
+
('🎫 Ticket Information', {
|
133
|
+
"fields": (("uuid", "user"), "subject", "status", "created_at"),
|
134
|
+
"classes": ("tab",)
|
87
135
|
}),
|
88
|
-
(
|
89
|
-
"description": "Use the
|
136
|
+
('💬 Chat Interface', {
|
137
|
+
"description": "Use the Chat interface to reply to this ticket. Click the Chat button.",
|
90
138
|
"fields": (),
|
91
|
-
"classes": ("collapse"
|
139
|
+
"classes": ("tab", "collapse")
|
92
140
|
}),
|
93
141
|
)
|
94
142
|
|
143
|
+
@display(description="User")
|
144
|
+
def user_display(self, obj: Ticket) -> str:
|
145
|
+
"""Display user with avatar representation."""
|
146
|
+
if not obj.user:
|
147
|
+
return "—"
|
148
|
+
|
149
|
+
# Use simple user display from DisplayMixin
|
150
|
+
return self.display_user_simple(obj.user)
|
95
151
|
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
initials = obj.user.__class__.objects.get_initials(obj.user)
|
105
|
-
bg_color = '#0d6efd' if obj.user.is_staff else '#6f42c1' if obj.user.is_superuser else '#198754'
|
106
|
-
|
107
|
-
return format_html(
|
108
|
-
'<div style="width: 32px; height: 32px; border-radius: 50%; background: {}; '
|
109
|
-
'color: white; display: flex; align-items: center; justify-content: center; '
|
110
|
-
'font-weight: bold; font-size: 12px;">{}</div>',
|
111
|
-
bg_color, initials
|
112
|
-
)
|
113
|
-
user_avatar.short_description = "User"
|
114
|
-
|
115
|
-
def uuid_link(self, obj):
|
116
|
-
"""Make UUID clickable link to ticket detail."""
|
117
|
-
url = reverse('admin:django_cfg_support_ticket_change', args=[obj.uuid])
|
118
|
-
return format_html(
|
119
|
-
'<a href="{}" style="font-family: monospace; font-size: 11px; background: #f8f9fa; '
|
120
|
-
'padding: 2px 4px; border-radius: 3px; text-decoration: none; color: #0d6efd;">{}</a>',
|
121
|
-
url, str(obj.uuid)[:8] + '...'
|
152
|
+
@display(description="UUID", ordering="uuid")
|
153
|
+
def uuid_display(self, obj: Ticket) -> str:
|
154
|
+
"""Display ticket UUID."""
|
155
|
+
config = StatusBadgeConfig(show_icons=True, icon=Icons.CONFIRMATION_NUMBER)
|
156
|
+
return StatusBadge.create(
|
157
|
+
text=str(obj.uuid)[:8] + "...",
|
158
|
+
variant="secondary",
|
159
|
+
config=config
|
122
160
|
)
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
'
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
161
|
+
|
162
|
+
@display(description="Subject", ordering="subject")
|
163
|
+
def subject_display(self, obj: Ticket) -> str:
|
164
|
+
"""Display ticket subject."""
|
165
|
+
config = StatusBadgeConfig(show_icons=True, icon=Icons.SUBJECT)
|
166
|
+
return StatusBadge.create(
|
167
|
+
text=obj.subject,
|
168
|
+
variant="primary",
|
169
|
+
config=config
|
170
|
+
)
|
171
|
+
|
172
|
+
@display(description="Status")
|
173
|
+
def status_display(self, obj: Ticket) -> str:
|
174
|
+
"""Display ticket status with color coding."""
|
175
|
+
status_config = StatusBadgeConfig(
|
176
|
+
custom_mappings={
|
177
|
+
'open': 'info',
|
178
|
+
'waiting_for_user': 'warning',
|
179
|
+
'waiting_for_admin': 'primary',
|
180
|
+
'resolved': 'success',
|
181
|
+
'closed': 'secondary'
|
182
|
+
},
|
183
|
+
show_icons=True,
|
184
|
+
icon=Icons.NEW_RELEASES if obj.status == 'open' else Icons.PENDING if obj.status == 'waiting_for_user' else Icons.SUPPORT_AGENT if obj.status == 'waiting_for_admin' else Icons.CHECK_CIRCLE if obj.status == 'resolved' else Icons.ARCHIVE
|
185
|
+
)
|
186
|
+
return self.display_status_auto(obj, 'status', status_config)
|
187
|
+
|
188
|
+
@display(description="Last Message")
|
189
|
+
def last_message_display(self, obj: Ticket) -> str:
|
190
|
+
"""Display last message preview."""
|
191
|
+
last_message = obj.message_set.order_by('-created_at').first()
|
192
|
+
if not last_message:
|
193
|
+
return "No messages"
|
194
|
+
|
195
|
+
preview = last_message.text[:50]
|
196
|
+
if len(last_message.text) > 50:
|
197
|
+
preview += "..."
|
198
|
+
|
199
|
+
return preview
|
200
|
+
|
201
|
+
@display(description="Last Activity")
|
202
|
+
def last_message_ago_display(self, obj: Ticket) -> str:
|
203
|
+
"""Display time since last message."""
|
204
|
+
last_message = obj.message_set.order_by('-created_at').first()
|
205
|
+
if not last_message:
|
206
|
+
return "—"
|
207
|
+
|
208
|
+
config = DateTimeDisplayConfig(show_relative=True)
|
209
|
+
return self.display_datetime_relative(last_message, 'created_at', config)
|
210
|
+
|
211
|
+
@display(description="Chat")
|
212
|
+
def chat_link_display(self, obj: Ticket) -> str:
|
213
|
+
"""Display chat link."""
|
214
|
+
config = StatusBadgeConfig(show_icons=True, icon=Icons.CHAT)
|
215
|
+
return StatusBadge.create(
|
216
|
+
text="Open Chat",
|
217
|
+
variant="info",
|
218
|
+
config=config
|
152
219
|
)
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
220
|
+
|
221
|
+
@display(description="Created")
|
222
|
+
def created_at_display(self, obj: Ticket) -> str:
|
223
|
+
"""Created time with relative display."""
|
224
|
+
config = DateTimeDisplayConfig(show_relative=True)
|
225
|
+
return self.display_datetime_relative(obj, 'created_at', config)
|
226
|
+
|
227
|
+
@action(description="Mark as open", variant=ActionVariant.INFO)
|
228
|
+
def mark_as_open(self, request: HttpRequest, queryset) -> None:
|
229
|
+
"""Mark selected tickets as open."""
|
230
|
+
count = queryset.update(status='open')
|
231
|
+
messages.info(request, f"Marked {count} tickets as open.")
|
232
|
+
|
233
|
+
@action(description="Mark as waiting for user", variant=ActionVariant.WARNING)
|
234
|
+
def mark_as_waiting_for_user(self, request: HttpRequest, queryset) -> None:
|
235
|
+
"""Mark selected tickets as waiting for user."""
|
236
|
+
count = queryset.update(status='waiting_for_user')
|
237
|
+
messages.warning(request, f"Marked {count} tickets as waiting for user.")
|
238
|
+
|
239
|
+
@action(description="Mark as waiting for admin", variant=ActionVariant.PRIMARY)
|
240
|
+
def mark_as_waiting_for_admin(self, request: HttpRequest, queryset) -> None:
|
241
|
+
"""Mark selected tickets as waiting for admin."""
|
242
|
+
count = queryset.update(status='waiting_for_admin')
|
243
|
+
messages.info(request, f"Marked {count} tickets as waiting for admin.")
|
244
|
+
|
245
|
+
@action(description="Mark as resolved", variant=ActionVariant.SUCCESS)
|
246
|
+
def mark_as_resolved(self, request: HttpRequest, queryset) -> None:
|
247
|
+
"""Mark selected tickets as resolved."""
|
248
|
+
count = queryset.update(status='resolved')
|
249
|
+
messages.success(request, f"Marked {count} tickets as resolved.")
|
250
|
+
|
251
|
+
@action(description="Mark as closed", variant=ActionVariant.DANGER)
|
252
|
+
def mark_as_closed(self, request: HttpRequest, queryset) -> None:
|
253
|
+
"""Mark selected tickets as closed."""
|
254
|
+
count = queryset.update(status='closed')
|
255
|
+
messages.error(request, f"Marked {count} tickets as closed.")
|
256
|
+
|
257
|
+
def changelist_view(self, request, extra_context=None):
|
258
|
+
"""Add ticket statistics to changelist."""
|
259
|
+
extra_context = extra_context or {}
|
260
|
+
|
261
|
+
queryset = self.get_queryset(request)
|
262
|
+
stats = queryset.aggregate(
|
263
|
+
total_tickets=Count('uuid'),
|
264
|
+
open_tickets=Count('uuid', filter=Q(status='open')),
|
265
|
+
waiting_for_user_tickets=Count('uuid', filter=Q(status='waiting_for_user')),
|
266
|
+
waiting_for_admin_tickets=Count('uuid', filter=Q(status='waiting_for_admin')),
|
267
|
+
resolved_tickets=Count('uuid', filter=Q(status='resolved')),
|
268
|
+
closed_tickets=Count('uuid', filter=Q(status='closed'))
|
269
|
+
)
|
270
|
+
|
271
|
+
extra_context['ticket_stats'] = {
|
272
|
+
'total_tickets': stats['total_tickets'] or 0,
|
273
|
+
'open_tickets': stats['open_tickets'] or 0,
|
274
|
+
'waiting_for_user_tickets': stats['waiting_for_user_tickets'] or 0,
|
275
|
+
'waiting_for_admin_tickets': stats['waiting_for_admin_tickets'] or 0,
|
276
|
+
'resolved_tickets': stats['resolved_tickets'] or 0,
|
277
|
+
'closed_tickets': stats['closed_tickets'] or 0
|
278
|
+
}
|
279
|
+
|
280
|
+
return super().changelist_view(request, extra_context)
|
281
|
+
|
165
282
|
|
166
283
|
@admin.register(Message)
|
167
|
-
class MessageAdmin(ModelAdmin, ExportMixin):
|
284
|
+
class MessageAdmin(OptimizedModelAdmin, DisplayMixin, ModelAdmin, ExportMixin):
|
285
|
+
"""Admin interface for Message using Django Admin Utilities."""
|
286
|
+
|
287
|
+
# Performance optimization
|
288
|
+
select_related_fields = ['ticket', 'sender']
|
289
|
+
|
168
290
|
# Export-only configuration
|
169
291
|
resource_class = MessageResource
|
170
292
|
export_form_class = ExportForm
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
293
|
+
|
294
|
+
list_display = [
|
295
|
+
"ticket_display", "sender_display", "text_preview", "created_at_display"
|
296
|
+
]
|
297
|
+
list_display_links = ["text_preview"]
|
298
|
+
ordering = ["-created_at"]
|
299
|
+
search_fields = ["ticket__uuid", "ticket__subject", "sender__username", "sender__email", "text"]
|
300
|
+
list_filter = ["created_at", "ticket__status", MessageSenderEmailFilter]
|
301
|
+
readonly_fields = ["ticket", "sender", "created_at"]
|
302
|
+
|
303
|
+
fieldsets = [
|
304
|
+
('💬 Message Information', {
|
305
|
+
'fields': ['ticket', 'sender', 'text'],
|
306
|
+
'classes': ('tab',)
|
180
307
|
}),
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
308
|
+
('⏰ Timestamps', {
|
309
|
+
'fields': ['created_at'],
|
310
|
+
'classes': ('tab', 'collapse')
|
311
|
+
})
|
312
|
+
]
|
313
|
+
|
314
|
+
def has_add_permission(self, request):
|
315
|
+
"""Disable adding messages through admin - use chat interface instead."""
|
316
|
+
return False
|
317
|
+
|
318
|
+
def has_change_permission(self, request, obj=None):
|
319
|
+
"""Disable editing messages through admin."""
|
320
|
+
return False
|
321
|
+
|
322
|
+
@display(description="Ticket")
|
323
|
+
def ticket_display(self, obj: Message) -> str:
|
324
|
+
"""Display ticket information."""
|
325
|
+
if not obj.ticket:
|
326
|
+
return "—"
|
327
|
+
|
328
|
+
config = StatusBadgeConfig(show_icons=True, icon=Icons.CONFIRMATION_NUMBER)
|
329
|
+
return StatusBadge.create(
|
330
|
+
text=f"{obj.ticket.subject} ({str(obj.ticket.uuid)[:8]}...)",
|
331
|
+
variant="primary",
|
332
|
+
config=config
|
333
|
+
)
|
334
|
+
|
335
|
+
@display(description="Sender")
|
336
|
+
def sender_display(self, obj: Message) -> str:
|
337
|
+
"""Display sender with role indication."""
|
338
|
+
if not obj.sender:
|
339
|
+
return "—"
|
340
|
+
|
341
|
+
# Determine sender type and variant
|
342
|
+
if obj.sender.is_superuser:
|
343
|
+
variant = "danger"
|
344
|
+
icon = Icons.ADMIN_PANEL_SETTINGS
|
345
|
+
elif obj.sender.is_staff:
|
346
|
+
variant = "primary"
|
347
|
+
icon = Icons.SUPPORT_AGENT
|
190
348
|
else:
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
349
|
+
variant = "info"
|
350
|
+
icon = Icons.PERSON
|
351
|
+
|
352
|
+
config = StatusBadgeConfig(show_icons=True, icon=icon)
|
353
|
+
return StatusBadge.create(
|
354
|
+
text=obj.sender.get_full_name() or obj.sender.username,
|
355
|
+
variant=variant,
|
356
|
+
config=config
|
357
|
+
)
|
358
|
+
|
359
|
+
@display(description="Message", ordering="text")
|
360
|
+
def text_preview(self, obj: Message) -> str:
|
361
|
+
"""Display message text preview."""
|
362
|
+
if not obj.text:
|
363
|
+
return "—"
|
364
|
+
|
365
|
+
preview = obj.text[:100]
|
366
|
+
if len(obj.text) > 100:
|
367
|
+
preview += "..."
|
368
|
+
|
369
|
+
return preview
|
370
|
+
|
371
|
+
@display(description="Created")
|
372
|
+
def created_at_display(self, obj: Message) -> str:
|
373
|
+
"""Created time with relative display."""
|
374
|
+
config = DateTimeDisplayConfig(show_relative=True)
|
375
|
+
return self.display_datetime_relative(obj, 'created_at', config)
|
201
376
|
|
202
|
-
def
|
203
|
-
"""
|
204
|
-
|
205
|
-
|
377
|
+
def changelist_view(self, request, extra_context=None):
|
378
|
+
"""Add message statistics to changelist."""
|
379
|
+
extra_context = extra_context or {}
|
380
|
+
|
381
|
+
queryset = self.get_queryset(request)
|
382
|
+
stats = queryset.aggregate(
|
383
|
+
total_messages=Count('uuid'),
|
384
|
+
staff_messages=Count('uuid', filter=Q(sender__is_staff=True)),
|
385
|
+
user_messages=Count('uuid', filter=Q(sender__is_staff=False))
|
386
|
+
)
|
387
|
+
|
388
|
+
# Messages by ticket status
|
389
|
+
ticket_status_counts = dict(
|
390
|
+
queryset.values_list('ticket__status').annotate(
|
391
|
+
count=Count('uuid')
|
392
|
+
)
|
393
|
+
)
|
394
|
+
|
395
|
+
extra_context['message_stats'] = {
|
396
|
+
'total_messages': stats['total_messages'] or 0,
|
397
|
+
'staff_messages': stats['staff_messages'] or 0,
|
398
|
+
'user_messages': stats['user_messages'] or 0,
|
399
|
+
'ticket_status_counts': ticket_status_counts
|
400
|
+
}
|
401
|
+
|
402
|
+
return super().changelist_view(request, extra_context)
|