django-cfg 1.3.7__py3-none-any.whl → 1.3.11__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 +269 -0
- django_cfg/apps/payments/admin/payments_admin.py +183 -460
- 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 +153 -34
- django_cfg/apps/payments/admin_interface/templates/payments/components/payment_card.html +121 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/payment_qr_code.html +95 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/progress_bar.html +37 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/provider_stats.html +60 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/status_badge.html +41 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/status_overview.html +83 -0
- django_cfg/apps/payments/admin_interface/templates/payments/payment_detail.html +363 -0
- django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +43 -17
- django_cfg/apps/payments/admin_interface/views/__init__.py +2 -0
- django_cfg/apps/payments/admin_interface/views/api/payments.py +102 -0
- django_cfg/apps/payments/admin_interface/views/api/webhook_admin.py +109 -63
- django_cfg/apps/payments/admin_interface/views/forms.py +5 -1
- django_cfg/apps/payments/config/__init__.py +14 -15
- django_cfg/apps/payments/config/django_cfg_integration.py +59 -1
- django_cfg/apps/payments/config/helpers.py +8 -13
- 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/0001_initial.py +33 -46
- django_cfg/apps/payments/migrations/0002_rename_payments_un_user_id_7f6e79_idx_payments_un_user_id_8ce187_idx_and_more.py +46 -0
- django_cfg/apps/payments/migrations/0003_universalpayment_status_changed_at.py +25 -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/models/managers/payment_managers.py +142 -25
- django_cfg/apps/payments/models/payments.py +94 -0
- django_cfg/apps/payments/services/core/base.py +4 -4
- django_cfg/apps/payments/services/core/currency_service.py +35 -28
- django_cfg/apps/payments/services/core/payment_service.py +266 -39
- django_cfg/apps/payments/services/providers/__init__.py +3 -0
- django_cfg/apps/payments/services/providers/base.py +303 -41
- django_cfg/apps/payments/services/providers/models/__init__.py +42 -0
- django_cfg/apps/payments/services/providers/models/base.py +145 -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/provider.py +557 -0
- django_cfg/apps/payments/services/providers/nowpayments/sync.py +196 -0
- django_cfg/apps/payments/services/providers/registry.py +9 -37
- django_cfg/apps/payments/services/providers/sync_service.py +277 -0
- django_cfg/apps/payments/services/types/requests.py +19 -7
- django_cfg/apps/payments/signals/payment_signals.py +31 -2
- django_cfg/apps/payments/static/payments/js/api-client.js +29 -6
- django_cfg/apps/payments/static/payments/js/payment-detail.js +167 -0
- django_cfg/apps/payments/static/payments/js/payment-form.js +98 -32
- 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/templatetags/payment_tags.py +8 -0
- django_cfg/apps/payments/urls.py +3 -2
- django_cfg/apps/payments/urls_admin.py +1 -1
- django_cfg/apps/payments/views/api/currencies.py +8 -5
- django_cfg/apps/payments/views/overview/services.py +2 -2
- django_cfg/apps/payments/views/serializers/currencies.py +22 -8
- 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/tasks/static/tasks/css/dashboard.css +68 -217
- django_cfg/apps/tasks/static/tasks/js/api.js +40 -84
- django_cfg/apps/tasks/static/tasks/js/components/DataManager.js +24 -0
- django_cfg/apps/tasks/static/tasks/js/components/TabManager.js +85 -0
- django_cfg/apps/tasks/static/tasks/js/components/TaskRenderer.js +216 -0
- django_cfg/apps/tasks/static/tasks/js/dashboard/main.mjs +245 -0
- django_cfg/apps/tasks/static/tasks/js/dashboard/overview.mjs +123 -0
- django_cfg/apps/tasks/static/tasks/js/dashboard/queues.mjs +120 -0
- django_cfg/apps/tasks/static/tasks/js/dashboard/tasks.mjs +350 -0
- django_cfg/apps/tasks/static/tasks/js/dashboard/workers.mjs +169 -0
- django_cfg/apps/tasks/tasks/__init__.py +10 -0
- django_cfg/apps/tasks/tasks/demo_tasks.py +133 -0
- django_cfg/apps/tasks/templates/tasks/components/management_actions.html +42 -45
- django_cfg/apps/tasks/templates/tasks/components/{status_cards.html → overview_content.html} +30 -11
- django_cfg/apps/tasks/templates/tasks/components/queues_content.html +19 -0
- django_cfg/apps/tasks/templates/tasks/components/tab_navigation.html +16 -10
- django_cfg/apps/tasks/templates/tasks/components/tasks_content.html +51 -0
- django_cfg/apps/tasks/templates/tasks/components/workers_content.html +30 -0
- django_cfg/apps/tasks/templates/tasks/layout/base.html +117 -0
- django_cfg/apps/tasks/templates/tasks/pages/dashboard.html +82 -0
- django_cfg/apps/tasks/templates/tasks/partials/task_row_template.html +40 -0
- django_cfg/apps/tasks/templates/tasks/widgets/task_filters.html +37 -0
- django_cfg/apps/tasks/templates/tasks/widgets/task_footer.html +41 -0
- django_cfg/apps/tasks/templates/tasks/widgets/task_table.html +50 -0
- django_cfg/apps/tasks/urls.py +2 -2
- django_cfg/apps/tasks/urls_admin.py +2 -2
- django_cfg/apps/tasks/utils/__init__.py +1 -0
- django_cfg/apps/tasks/utils/simulator.py +356 -0
- django_cfg/apps/tasks/views/__init__.py +16 -0
- django_cfg/apps/tasks/views/api.py +569 -0
- django_cfg/apps/tasks/views/dashboard.py +58 -0
- django_cfg/config.py +1 -1
- django_cfg/core/config.py +10 -5
- django_cfg/core/generation.py +1 -1
- django_cfg/core/integration/__init__.py +21 -0
- django_cfg/management/commands/__init__.py +13 -1
- 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/management/commands/rundramatiq_simulator.py +430 -0
- django_cfg/middleware/__init__.py +0 -2
- django_cfg/models/api_keys.py +115 -0
- django_cfg/models/constance.py +0 -11
- django_cfg/models/payments.py +137 -3
- 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_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_tasks.py +54 -21
- 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 +7 -9
- 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.11.dist-info}/METADATA +2 -1
- {django_cfg-1.3.7.dist-info → django_cfg-1.3.11.dist-info}/RECORD +198 -160
- 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/payments/config/constance/__init__.py +0 -22
- django_cfg/apps/payments/config/constance/config_service.py +0 -123
- django_cfg/apps/payments/config/constance/fields.py +0 -69
- django_cfg/apps/payments/config/constance/settings.py +0 -160
- django_cfg/apps/payments/services/providers/nowpayments.py +0 -478
- django_cfg/apps/tasks/admin.py +0 -320
- django_cfg/apps/tasks/static/tasks/js/dashboard.js +0 -614
- django_cfg/apps/tasks/static/tasks/js/modals.js +0 -452
- django_cfg/apps/tasks/static/tasks/js/notifications.js +0 -144
- django_cfg/apps/tasks/static/tasks/js/task-monitor.js +0 -454
- django_cfg/apps/tasks/static/tasks/js/theme.js +0 -77
- django_cfg/apps/tasks/templates/tasks/base.html +0 -96
- django_cfg/apps/tasks/templates/tasks/components/info_cards.html +0 -85
- django_cfg/apps/tasks/templates/tasks/components/overview_tab.html +0 -22
- django_cfg/apps/tasks/templates/tasks/components/queues_tab.html +0 -19
- django_cfg/apps/tasks/templates/tasks/components/task_details_modal.html +0 -103
- django_cfg/apps/tasks/templates/tasks/components/tasks_tab.html +0 -32
- django_cfg/apps/tasks/templates/tasks/components/workers_tab.html +0 -29
- django_cfg/apps/tasks/templates/tasks/dashboard.html +0 -29
- django_cfg/apps/tasks/views.py +0 -461
- django_cfg/management/commands/auto_generate.py +0 -486
- 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.11.dist-info}/WHEEL +0 -0
- {django_cfg-1.3.7.dist-info → django_cfg-1.3.11.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.3.7.dist-info → django_cfg-1.3.11.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,327 @@
|
|
1
|
+
"""
|
2
|
+
Twilio Response admin interface using Django Admin Utilities.
|
3
|
+
|
4
|
+
Enhanced Twilio response management with Material Icons and optimized queries.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from django.contrib import admin
|
8
|
+
from django.utils.html import format_html
|
9
|
+
from unfold.admin import ModelAdmin
|
10
|
+
from django_cfg import ExportMixin, ExportForm
|
11
|
+
|
12
|
+
from django_cfg.modules.django_admin import (
|
13
|
+
OptimizedModelAdmin,
|
14
|
+
DisplayMixin,
|
15
|
+
MoneyDisplayConfig,
|
16
|
+
DateTimeDisplayConfig,
|
17
|
+
StatusBadgeConfig,
|
18
|
+
Icons,
|
19
|
+
display
|
20
|
+
)
|
21
|
+
from django_cfg.modules.django_admin.utils.badges import StatusBadge
|
22
|
+
|
23
|
+
from ..models import TwilioResponse
|
24
|
+
from .filters import TwilioResponseStatusFilter, TwilioResponseTypeFilter
|
25
|
+
from .resources import TwilioResponseResource
|
26
|
+
|
27
|
+
|
28
|
+
class TwilioResponseInline(admin.TabularInline):
|
29
|
+
"""Inline for showing Twilio responses in related models."""
|
30
|
+
model = TwilioResponse
|
31
|
+
extra = 0
|
32
|
+
readonly_fields = ['created_at', 'status', 'message_sid', 'error_code']
|
33
|
+
fields = ['response_type', 'service_type', 'status', 'message_sid', 'error_code', 'created_at']
|
34
|
+
|
35
|
+
def has_add_permission(self, request, obj=None):
|
36
|
+
return False
|
37
|
+
|
38
|
+
|
39
|
+
|
40
|
+
@admin.register(TwilioResponse)
|
41
|
+
class TwilioResponseAdmin(OptimizedModelAdmin, DisplayMixin, ModelAdmin, ExportMixin):
|
42
|
+
"""Enhanced Twilio Response admin using Django Admin Utilities."""
|
43
|
+
|
44
|
+
# Export configuration
|
45
|
+
resource_class = TwilioResponseResource
|
46
|
+
export_form_class = ExportForm
|
47
|
+
|
48
|
+
# Performance optimization
|
49
|
+
select_related_fields = ['otp_secret']
|
50
|
+
|
51
|
+
list_display = [
|
52
|
+
'identifier_display',
|
53
|
+
'service_type_display',
|
54
|
+
'response_type_display',
|
55
|
+
'status_display',
|
56
|
+
'recipient_display',
|
57
|
+
'price_display',
|
58
|
+
'created_display',
|
59
|
+
'error_status_display'
|
60
|
+
]
|
61
|
+
list_display_links = ['identifier_display']
|
62
|
+
list_filter = [
|
63
|
+
TwilioResponseStatusFilter,
|
64
|
+
TwilioResponseTypeFilter,
|
65
|
+
'service_type',
|
66
|
+
'response_type',
|
67
|
+
'created_at',
|
68
|
+
]
|
69
|
+
search_fields = [
|
70
|
+
'message_sid',
|
71
|
+
'verification_sid',
|
72
|
+
'to_number',
|
73
|
+
'error_message',
|
74
|
+
'otp_secret__recipient'
|
75
|
+
]
|
76
|
+
readonly_fields = [
|
77
|
+
'created_at',
|
78
|
+
'updated_at',
|
79
|
+
'twilio_created_at',
|
80
|
+
'response_data_display',
|
81
|
+
'request_data_display'
|
82
|
+
]
|
83
|
+
ordering = ['-created_at']
|
84
|
+
|
85
|
+
fieldsets = (
|
86
|
+
(
|
87
|
+
'Basic Information',
|
88
|
+
{
|
89
|
+
'fields': (
|
90
|
+
'response_type',
|
91
|
+
'service_type',
|
92
|
+
'status',
|
93
|
+
'otp_secret'
|
94
|
+
),
|
95
|
+
},
|
96
|
+
),
|
97
|
+
(
|
98
|
+
'Twilio Identifiers',
|
99
|
+
{
|
100
|
+
'fields': (
|
101
|
+
'message_sid',
|
102
|
+
'verification_sid',
|
103
|
+
),
|
104
|
+
},
|
105
|
+
),
|
106
|
+
(
|
107
|
+
'Recipients',
|
108
|
+
{
|
109
|
+
'fields': (
|
110
|
+
'to_number',
|
111
|
+
'from_number',
|
112
|
+
),
|
113
|
+
},
|
114
|
+
),
|
115
|
+
(
|
116
|
+
'Error Information',
|
117
|
+
{
|
118
|
+
'fields': (
|
119
|
+
'error_code',
|
120
|
+
'error_message',
|
121
|
+
),
|
122
|
+
'classes': ('collapse',),
|
123
|
+
},
|
124
|
+
),
|
125
|
+
(
|
126
|
+
'Pricing',
|
127
|
+
{
|
128
|
+
'fields': (
|
129
|
+
'price',
|
130
|
+
'price_unit',
|
131
|
+
),
|
132
|
+
'classes': ('collapse',),
|
133
|
+
},
|
134
|
+
),
|
135
|
+
(
|
136
|
+
'Request/Response Data',
|
137
|
+
{
|
138
|
+
'fields': (
|
139
|
+
'request_data_display',
|
140
|
+
'response_data_display',
|
141
|
+
),
|
142
|
+
'classes': ('collapse',),
|
143
|
+
},
|
144
|
+
),
|
145
|
+
(
|
146
|
+
'Timestamps',
|
147
|
+
{
|
148
|
+
'fields': (
|
149
|
+
'created_at',
|
150
|
+
'updated_at',
|
151
|
+
'twilio_created_at',
|
152
|
+
),
|
153
|
+
'classes': ('collapse',),
|
154
|
+
},
|
155
|
+
),
|
156
|
+
)
|
157
|
+
|
158
|
+
@display(description="Identifier")
|
159
|
+
def identifier_display(self, obj):
|
160
|
+
"""Main identifier display with appropriate icon."""
|
161
|
+
identifier = obj.message_sid or obj.verification_sid
|
162
|
+
if not identifier:
|
163
|
+
return "—"
|
164
|
+
|
165
|
+
# Truncate long identifiers
|
166
|
+
if len(identifier) > 20:
|
167
|
+
identifier = f"{identifier[:17]}..."
|
168
|
+
|
169
|
+
config = StatusBadgeConfig(show_icons=True, icon=Icons.FINGERPRINT)
|
170
|
+
return StatusBadge.create(
|
171
|
+
text=identifier,
|
172
|
+
variant="info",
|
173
|
+
config=config
|
174
|
+
)
|
175
|
+
|
176
|
+
@display(description="Service")
|
177
|
+
def service_type_display(self, obj):
|
178
|
+
"""Service type display with appropriate icon."""
|
179
|
+
service_icons = {
|
180
|
+
'sms': Icons.SMS,
|
181
|
+
'voice': Icons.PHONE,
|
182
|
+
'verify': Icons.VERIFIED,
|
183
|
+
'email': Icons.EMAIL,
|
184
|
+
}
|
185
|
+
|
186
|
+
icon = service_icons.get(obj.service_type, Icons.CLOUD)
|
187
|
+
|
188
|
+
config = StatusBadgeConfig(show_icons=True, icon=icon)
|
189
|
+
return StatusBadge.create(
|
190
|
+
text=obj.get_service_type_display(),
|
191
|
+
variant="primary",
|
192
|
+
config=config
|
193
|
+
)
|
194
|
+
|
195
|
+
@display(description="Type")
|
196
|
+
def response_type_display(self, obj):
|
197
|
+
"""Response type display with appropriate icon."""
|
198
|
+
type_icons = {
|
199
|
+
'send': Icons.SEND,
|
200
|
+
'verify': Icons.VERIFIED,
|
201
|
+
'check': Icons.CHECK_CIRCLE,
|
202
|
+
}
|
203
|
+
|
204
|
+
icon = type_icons.get(obj.response_type, Icons.DESCRIPTION)
|
205
|
+
|
206
|
+
config = StatusBadgeConfig(show_icons=True, icon=icon)
|
207
|
+
return StatusBadge.create(
|
208
|
+
text=obj.get_response_type_display(),
|
209
|
+
variant="info",
|
210
|
+
config=config
|
211
|
+
)
|
212
|
+
|
213
|
+
@display(description="Status", label=True)
|
214
|
+
def status_display(self, obj):
|
215
|
+
"""Enhanced status display with appropriate colors and icons."""
|
216
|
+
if obj.has_error:
|
217
|
+
status = obj.status or 'Error'
|
218
|
+
icon = Icons.ERROR
|
219
|
+
variant = "danger"
|
220
|
+
elif obj.is_successful:
|
221
|
+
status = obj.status or 'Success'
|
222
|
+
icon = Icons.CHECK_CIRCLE
|
223
|
+
variant = "success"
|
224
|
+
else:
|
225
|
+
status = obj.status or 'Pending'
|
226
|
+
icon = Icons.SCHEDULE
|
227
|
+
variant = "warning"
|
228
|
+
|
229
|
+
config = StatusBadgeConfig(
|
230
|
+
show_icons=True,
|
231
|
+
icon=icon,
|
232
|
+
custom_mappings={status: variant}
|
233
|
+
)
|
234
|
+
|
235
|
+
return self.display_status_auto(
|
236
|
+
type('obj', (), {'status': status})(),
|
237
|
+
'status',
|
238
|
+
config
|
239
|
+
)
|
240
|
+
|
241
|
+
@display(description="Recipient")
|
242
|
+
def recipient_display(self, obj):
|
243
|
+
"""Recipient display with privacy masking."""
|
244
|
+
if not obj.to_number:
|
245
|
+
return "—"
|
246
|
+
|
247
|
+
# Mask phone numbers and emails for privacy
|
248
|
+
recipient = obj.to_number
|
249
|
+
if '@' in recipient:
|
250
|
+
# Email masking
|
251
|
+
local, domain = recipient.split('@', 1)
|
252
|
+
masked_local = local[:2] + '*' * (len(local) - 2) if len(local) > 2 else local
|
253
|
+
masked_recipient = f"{masked_local}@{domain}"
|
254
|
+
icon = Icons.EMAIL
|
255
|
+
else:
|
256
|
+
# Phone masking
|
257
|
+
masked_recipient = f"***{recipient[-4:]}" if len(recipient) > 4 else "***"
|
258
|
+
icon = Icons.PHONE
|
259
|
+
|
260
|
+
config = StatusBadgeConfig(show_icons=True, icon=icon)
|
261
|
+
return StatusBadge.create(
|
262
|
+
text=masked_recipient,
|
263
|
+
variant="secondary",
|
264
|
+
config=config
|
265
|
+
)
|
266
|
+
|
267
|
+
@display(description="Price")
|
268
|
+
def price_display(self, obj):
|
269
|
+
"""Price display with currency formatting."""
|
270
|
+
if not obj.price or not obj.price_unit:
|
271
|
+
return "—"
|
272
|
+
|
273
|
+
config = MoneyDisplayConfig(
|
274
|
+
currency=obj.price_unit.upper(),
|
275
|
+
decimal_places=4,
|
276
|
+
show_sign=False
|
277
|
+
)
|
278
|
+
|
279
|
+
return self.display_money_amount(
|
280
|
+
type('obj', (), {'price': obj.price})(),
|
281
|
+
'price',
|
282
|
+
config
|
283
|
+
)
|
284
|
+
|
285
|
+
@display(description="Created")
|
286
|
+
def created_display(self, obj):
|
287
|
+
"""Created time with relative display."""
|
288
|
+
config = DateTimeDisplayConfig(show_relative=True)
|
289
|
+
return self.display_datetime_relative(obj, 'created_at', config)
|
290
|
+
|
291
|
+
@display(description="Error")
|
292
|
+
def error_status_display(self, obj):
|
293
|
+
"""Error status indicator."""
|
294
|
+
if obj.has_error:
|
295
|
+
config = StatusBadgeConfig(show_icons=True, icon=Icons.ERROR)
|
296
|
+
return StatusBadge.create(text="Error", variant="danger", config=config)
|
297
|
+
|
298
|
+
config = StatusBadgeConfig(show_icons=True, icon=Icons.CHECK_CIRCLE)
|
299
|
+
return StatusBadge.create(text="OK", variant="success", config=config)
|
300
|
+
|
301
|
+
def request_data_display(self, obj):
|
302
|
+
"""Display formatted request data."""
|
303
|
+
if not obj.request_data:
|
304
|
+
return "—"
|
305
|
+
|
306
|
+
import json
|
307
|
+
try:
|
308
|
+
formatted = json.dumps(obj.request_data, indent=2, ensure_ascii=False)
|
309
|
+
return format_html('<pre style="font-size: 12px; max-height: 300px; overflow-y: auto;">{}</pre>', formatted)
|
310
|
+
except (TypeError, ValueError):
|
311
|
+
return str(obj.request_data)
|
312
|
+
|
313
|
+
request_data_display.short_description = 'Request Data'
|
314
|
+
|
315
|
+
def response_data_display(self, obj):
|
316
|
+
"""Display formatted response data."""
|
317
|
+
if not obj.response_data:
|
318
|
+
return "—"
|
319
|
+
|
320
|
+
import json
|
321
|
+
try:
|
322
|
+
formatted = json.dumps(obj.response_data, indent=2, ensure_ascii=False)
|
323
|
+
return format_html('<pre style="font-size: 12px; max-height: 300px; overflow-y: auto;">{}</pre>', formatted)
|
324
|
+
except (TypeError, ValueError):
|
325
|
+
return str(obj.response_data)
|
326
|
+
|
327
|
+
response_data_display.short_description = 'Response Data'
|
@@ -0,0 +1,362 @@
|
|
1
|
+
"""
|
2
|
+
User admin interface using Django Admin Utilities.
|
3
|
+
|
4
|
+
Enhanced user management with Material Icons, status badges, and optimized queries.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from django.contrib import admin
|
8
|
+
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
|
9
|
+
from django.shortcuts import redirect
|
10
|
+
from django.urls import reverse
|
11
|
+
from unfold.admin import ModelAdmin
|
12
|
+
from unfold.forms import AdminPasswordChangeForm, UserChangeForm, UserCreationForm
|
13
|
+
from django_cfg import ImportExportModelAdmin
|
14
|
+
|
15
|
+
from django_cfg.modules.base import BaseCfgModule
|
16
|
+
from django_cfg.modules.django_admin import (
|
17
|
+
OptimizedModelAdmin,
|
18
|
+
DisplayMixin,
|
19
|
+
StandaloneActionsMixin,
|
20
|
+
standalone_action,
|
21
|
+
UserDisplayConfig,
|
22
|
+
DateTimeDisplayConfig,
|
23
|
+
StatusBadgeConfig,
|
24
|
+
Icons,
|
25
|
+
ActionVariant,
|
26
|
+
display,
|
27
|
+
action
|
28
|
+
)
|
29
|
+
from django_cfg.modules.django_admin.utils.badges import StatusBadge
|
30
|
+
|
31
|
+
from ..models import CustomUser
|
32
|
+
from .filters import UserStatusFilter
|
33
|
+
from .inlines import UserRegistrationSourceInline, UserActivityInline, UserEmailLogInline, UserSupportTicketsInline
|
34
|
+
from .resources import CustomUserResource
|
35
|
+
|
36
|
+
|
37
|
+
@admin.register(CustomUser)
|
38
|
+
class CustomUserAdmin(BaseUserAdmin, OptimizedModelAdmin, DisplayMixin, StandaloneActionsMixin, ModelAdmin, ImportExportModelAdmin):
|
39
|
+
"""Enhanced user admin using Django Admin Utilities."""
|
40
|
+
|
41
|
+
# Import/Export configuration
|
42
|
+
resource_class = CustomUserResource
|
43
|
+
|
44
|
+
# Forms loaded from unfold.forms
|
45
|
+
form = UserChangeForm
|
46
|
+
add_form = UserCreationForm
|
47
|
+
change_password_form = AdminPasswordChangeForm
|
48
|
+
|
49
|
+
# Performance optimization
|
50
|
+
select_related_fields = []
|
51
|
+
prefetch_related_fields = ['groups', 'user_permissions']
|
52
|
+
|
53
|
+
list_display = [
|
54
|
+
'avatar_display',
|
55
|
+
'email_display',
|
56
|
+
'full_name_display',
|
57
|
+
'status_display',
|
58
|
+
'sources_count_display',
|
59
|
+
'activity_count_display',
|
60
|
+
'emails_count_display',
|
61
|
+
'tickets_count_display',
|
62
|
+
'last_login_display',
|
63
|
+
'date_joined_display',
|
64
|
+
]
|
65
|
+
list_display_links = ['avatar_display', 'email_display', 'full_name_display']
|
66
|
+
search_fields = ['email', 'first_name', 'last_name']
|
67
|
+
list_filter = [UserStatusFilter, 'is_staff', 'is_active', 'date_joined']
|
68
|
+
ordering = ['-date_joined']
|
69
|
+
readonly_fields = ['date_joined', 'last_login']
|
70
|
+
|
71
|
+
# Register standalone actions
|
72
|
+
actions_list = ['view_user_emails', 'view_user_tickets', 'export_user_data']
|
73
|
+
|
74
|
+
fieldsets = (
|
75
|
+
(
|
76
|
+
"Personal Information",
|
77
|
+
{
|
78
|
+
"fields": ("email", "first_name", "last_name", "avatar"),
|
79
|
+
},
|
80
|
+
),
|
81
|
+
(
|
82
|
+
"Contact Information",
|
83
|
+
{
|
84
|
+
"fields": ("company", "phone", "position"),
|
85
|
+
},
|
86
|
+
),
|
87
|
+
(
|
88
|
+
"Authentication",
|
89
|
+
{
|
90
|
+
"fields": ("password",),
|
91
|
+
"classes": ("collapse",),
|
92
|
+
},
|
93
|
+
),
|
94
|
+
(
|
95
|
+
"Permissions & Status",
|
96
|
+
{
|
97
|
+
"fields": (
|
98
|
+
("is_active", "is_staff", "is_superuser"),
|
99
|
+
("groups",),
|
100
|
+
("user_permissions",),
|
101
|
+
),
|
102
|
+
},
|
103
|
+
),
|
104
|
+
(
|
105
|
+
"Important Dates",
|
106
|
+
{
|
107
|
+
"fields": ("last_login", "date_joined"),
|
108
|
+
"classes": ("collapse",),
|
109
|
+
},
|
110
|
+
),
|
111
|
+
)
|
112
|
+
|
113
|
+
add_fieldsets = (
|
114
|
+
(
|
115
|
+
None,
|
116
|
+
{
|
117
|
+
"classes": ("wide",),
|
118
|
+
"fields": ("email", "password1", "password2"),
|
119
|
+
},
|
120
|
+
),
|
121
|
+
)
|
122
|
+
|
123
|
+
def get_inlines(self, request, obj):
|
124
|
+
"""Get inlines based on enabled apps."""
|
125
|
+
inlines = [UserRegistrationSourceInline, UserActivityInline]
|
126
|
+
|
127
|
+
# Add email log inline if newsletter app is enabled
|
128
|
+
try:
|
129
|
+
base_module = BaseCfgModule()
|
130
|
+
if base_module.is_newsletter_enabled():
|
131
|
+
inlines.append(UserEmailLogInline)
|
132
|
+
if base_module.is_support_enabled():
|
133
|
+
inlines.append(UserSupportTicketsInline)
|
134
|
+
except Exception:
|
135
|
+
pass
|
136
|
+
|
137
|
+
return inlines
|
138
|
+
|
139
|
+
@display(description="Avatar")
|
140
|
+
def avatar_display(self, obj):
|
141
|
+
"""Enhanced avatar display with fallback initials."""
|
142
|
+
config = UserDisplayConfig(
|
143
|
+
show_avatar=True,
|
144
|
+
avatar_size=32
|
145
|
+
)
|
146
|
+
return self.display_user_avatar(obj, config)
|
147
|
+
|
148
|
+
@display(description="Email")
|
149
|
+
def email_display(self, obj):
|
150
|
+
"""Email display with user icon."""
|
151
|
+
config = StatusBadgeConfig(show_icons=True, icon=Icons.EMAIL)
|
152
|
+
return StatusBadge.create(
|
153
|
+
text=obj.email,
|
154
|
+
variant="info",
|
155
|
+
config=config
|
156
|
+
)
|
157
|
+
|
158
|
+
@display(description="Full Name")
|
159
|
+
def full_name_display(self, obj):
|
160
|
+
"""Full name display."""
|
161
|
+
full_name = obj.__class__.objects.get_full_name(obj)
|
162
|
+
if not full_name:
|
163
|
+
config = StatusBadgeConfig(show_icons=True, icon=Icons.PERSON)
|
164
|
+
return StatusBadge.create(text="No name", variant="secondary", config=config)
|
165
|
+
|
166
|
+
config = StatusBadgeConfig(show_icons=True, icon=Icons.PERSON)
|
167
|
+
return StatusBadge.create(text=full_name, variant="primary", config=config)
|
168
|
+
|
169
|
+
@display(description="Status", label=True)
|
170
|
+
def status_display(self, obj):
|
171
|
+
"""Enhanced status display with appropriate icons and colors."""
|
172
|
+
if obj.is_superuser:
|
173
|
+
status = "Superuser"
|
174
|
+
icon = Icons.ADMIN_PANEL_SETTINGS
|
175
|
+
variant = "danger"
|
176
|
+
elif obj.is_staff:
|
177
|
+
status = "Staff"
|
178
|
+
icon = Icons.SETTINGS
|
179
|
+
variant = "warning"
|
180
|
+
elif obj.is_active:
|
181
|
+
status = "Active"
|
182
|
+
icon = Icons.CHECK_CIRCLE
|
183
|
+
variant = "success"
|
184
|
+
else:
|
185
|
+
status = "Inactive"
|
186
|
+
icon = Icons.CANCEL
|
187
|
+
variant = "secondary"
|
188
|
+
|
189
|
+
config = StatusBadgeConfig(
|
190
|
+
show_icons=True,
|
191
|
+
icon=icon,
|
192
|
+
custom_mappings={status: variant}
|
193
|
+
)
|
194
|
+
return self.display_status_auto(
|
195
|
+
type('obj', (), {'status': status})(),
|
196
|
+
'status',
|
197
|
+
config
|
198
|
+
)
|
199
|
+
|
200
|
+
@display(description="Sources")
|
201
|
+
def sources_count_display(self, obj):
|
202
|
+
"""Show count of registration sources for user."""
|
203
|
+
count = obj.user_registration_sources.count()
|
204
|
+
if count == 0:
|
205
|
+
return "—"
|
206
|
+
|
207
|
+
config = StatusBadgeConfig(show_icons=True, icon=Icons.SOURCE)
|
208
|
+
return StatusBadge.create(
|
209
|
+
text=f"{count} source{'s' if count != 1 else ''}",
|
210
|
+
variant="info",
|
211
|
+
config=config
|
212
|
+
)
|
213
|
+
|
214
|
+
@display(description="Activities")
|
215
|
+
def activity_count_display(self, obj):
|
216
|
+
"""Show count of user activities."""
|
217
|
+
count = obj.activities.count()
|
218
|
+
if count == 0:
|
219
|
+
return "—"
|
220
|
+
|
221
|
+
config = StatusBadgeConfig(show_icons=True, icon=Icons.HISTORY)
|
222
|
+
return StatusBadge.create(
|
223
|
+
text=f"{count} activit{'ies' if count != 1 else 'y'}",
|
224
|
+
variant="info",
|
225
|
+
config=config
|
226
|
+
)
|
227
|
+
|
228
|
+
@display(description="Emails")
|
229
|
+
def emails_count_display(self, obj):
|
230
|
+
"""Show count of emails sent to user (if newsletter app is enabled)."""
|
231
|
+
try:
|
232
|
+
base_module = BaseCfgModule()
|
233
|
+
|
234
|
+
if not base_module.is_newsletter_enabled():
|
235
|
+
return "—"
|
236
|
+
|
237
|
+
from django_cfg.apps.newsletter.models import EmailLog
|
238
|
+
count = EmailLog.objects.filter(user=obj).count()
|
239
|
+
if count == 0:
|
240
|
+
return "—"
|
241
|
+
|
242
|
+
config = StatusBadgeConfig(show_icons=True, icon=Icons.EMAIL)
|
243
|
+
return StatusBadge.create(
|
244
|
+
text=f"{count} email{'s' if count != 1 else ''}",
|
245
|
+
variant="success",
|
246
|
+
config=config
|
247
|
+
)
|
248
|
+
except (ImportError, Exception):
|
249
|
+
return "—"
|
250
|
+
|
251
|
+
@display(description="Tickets")
|
252
|
+
def tickets_count_display(self, obj):
|
253
|
+
"""Show count of support tickets for user (if support app is enabled)."""
|
254
|
+
try:
|
255
|
+
from django_cfg.modules.base import BaseCfgModule
|
256
|
+
base_module = BaseCfgModule()
|
257
|
+
|
258
|
+
if not base_module.is_support_enabled():
|
259
|
+
return "—"
|
260
|
+
|
261
|
+
from django_cfg.apps.support.models import Ticket
|
262
|
+
count = Ticket.objects.filter(user=obj).count()
|
263
|
+
if count == 0:
|
264
|
+
return "—"
|
265
|
+
|
266
|
+
config = StatusBadgeConfig(show_icons=True, icon=Icons.SUPPORT_AGENT)
|
267
|
+
return StatusBadge.create(
|
268
|
+
text=f"{count} ticket{'s' if count != 1 else ''}",
|
269
|
+
variant="warning",
|
270
|
+
config=config
|
271
|
+
)
|
272
|
+
except (ImportError, Exception):
|
273
|
+
return "—"
|
274
|
+
|
275
|
+
@display(description="Last Login")
|
276
|
+
def last_login_display(self, obj):
|
277
|
+
"""Last login with relative time."""
|
278
|
+
if not obj.last_login:
|
279
|
+
config = StatusBadgeConfig(show_icons=True, icon=Icons.LOGIN)
|
280
|
+
return StatusBadge.create(text="Never", variant="secondary", config=config)
|
281
|
+
|
282
|
+
config = DateTimeDisplayConfig(show_relative=True)
|
283
|
+
return self.display_datetime_relative(obj, 'last_login', config)
|
284
|
+
|
285
|
+
@display(description="Joined")
|
286
|
+
def date_joined_display(self, obj):
|
287
|
+
"""Join date with relative time."""
|
288
|
+
config = DateTimeDisplayConfig(show_relative=True)
|
289
|
+
return self.display_datetime_relative(obj, 'date_joined', config)
|
290
|
+
|
291
|
+
# Standalone Actions
|
292
|
+
@standalone_action(
|
293
|
+
description="📧 View Email History",
|
294
|
+
variant=ActionVariant.INFO,
|
295
|
+
icon="mail_outline",
|
296
|
+
success_message="Redirected to email history for {result}",
|
297
|
+
error_message="❌ Error accessing email history: {error}"
|
298
|
+
)
|
299
|
+
def view_user_emails(self, request, object_id):
|
300
|
+
"""View all emails sent to this user."""
|
301
|
+
# Get the user object
|
302
|
+
user = self.get_object(request, object_id)
|
303
|
+
if not user:
|
304
|
+
raise Exception("User not found")
|
305
|
+
|
306
|
+
# Check if newsletter app is enabled
|
307
|
+
from django_cfg.modules.base import BaseCfgModule
|
308
|
+
base_module = BaseCfgModule()
|
309
|
+
|
310
|
+
if not base_module.is_newsletter_enabled():
|
311
|
+
raise Exception("Newsletter app is not enabled")
|
312
|
+
|
313
|
+
# Redirect to EmailLog changelist filtered by this user
|
314
|
+
url = reverse('admin:django_cfg_newsletter_emaillog_changelist')
|
315
|
+
redirect_url = f"{url}?user__id__exact={user.id}"
|
316
|
+
|
317
|
+
# Return redirect (handled by standalone_action decorator)
|
318
|
+
return redirect(redirect_url)
|
319
|
+
|
320
|
+
@standalone_action(
|
321
|
+
description="🎫 View Support Tickets",
|
322
|
+
variant=ActionVariant.SUCCESS,
|
323
|
+
icon="support_agent",
|
324
|
+
success_message="Redirected to support tickets for {result}",
|
325
|
+
error_message="❌ Error accessing support tickets: {error}"
|
326
|
+
)
|
327
|
+
def view_user_tickets(self, request, object_id):
|
328
|
+
"""View all support tickets for this user."""
|
329
|
+
# Get the user object
|
330
|
+
user = self.get_object(request, object_id)
|
331
|
+
if not user:
|
332
|
+
raise Exception("User not found")
|
333
|
+
|
334
|
+
# Check if support app is enabled
|
335
|
+
from django_cfg.modules.base import BaseCfgModule
|
336
|
+
base_module = BaseCfgModule()
|
337
|
+
|
338
|
+
if not base_module.is_support_enabled():
|
339
|
+
raise Exception("Support app is not enabled")
|
340
|
+
|
341
|
+
# Redirect to Ticket changelist filtered by this user
|
342
|
+
url = reverse('admin:django_cfg_support_ticket_changelist')
|
343
|
+
redirect_url = f"{url}?user__id__exact={user.id}"
|
344
|
+
|
345
|
+
return redirect(redirect_url)
|
346
|
+
|
347
|
+
@standalone_action(
|
348
|
+
description="📊 Export User Data",
|
349
|
+
variant=ActionVariant.WARNING,
|
350
|
+
icon="download",
|
351
|
+
success_message="📊 User data export completed: {result}",
|
352
|
+
error_message="❌ Export failed: {error}"
|
353
|
+
)
|
354
|
+
def export_user_data(self, request, object_id):
|
355
|
+
"""Export comprehensive user data."""
|
356
|
+
user = self.get_object(request, object_id)
|
357
|
+
if not user:
|
358
|
+
raise Exception("User not found")
|
359
|
+
|
360
|
+
# Here you would implement actual export logic
|
361
|
+
# For now, just return a success message
|
362
|
+
return f"Data for {user.get_full_name() or user.email}"
|