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
@@ -1,548 +1,271 @@
|
|
1
1
|
"""
|
2
|
-
Payment Admin interface
|
2
|
+
Payment Admin interface using Django Admin Utilities.
|
3
3
|
|
4
|
-
|
4
|
+
Clean, modern payment management with no HTML duplication.
|
5
5
|
"""
|
6
6
|
|
7
7
|
from django.contrib import admin
|
8
|
-
from django.utils.html import format_html
|
9
|
-
from django.contrib.humanize.templatetags.humanize import naturaltime, intcomma
|
10
|
-
from django.contrib import messages
|
11
|
-
from django.shortcuts import redirect
|
12
|
-
from django.utils.safestring import mark_safe
|
13
8
|
from django.db.models import Count, Sum, Q
|
14
9
|
from django.utils import timezone
|
15
10
|
from datetime import timedelta
|
16
|
-
from typing import Optional
|
17
11
|
|
18
12
|
from unfold.admin import ModelAdmin
|
19
|
-
from unfold.decorators import display, action
|
20
|
-
from unfold.enums import ActionVariant
|
21
13
|
|
22
|
-
from
|
23
|
-
|
14
|
+
from django_cfg.modules.django_admin import (
|
15
|
+
OptimizedModelAdmin,
|
16
|
+
DisplayMixin,
|
17
|
+
UserDisplayConfig,
|
18
|
+
MoneyDisplayConfig,
|
19
|
+
StatusBadgeConfig,
|
20
|
+
DateTimeDisplayConfig,
|
21
|
+
Icons,
|
22
|
+
display,
|
23
|
+
action,
|
24
|
+
ActionVariant
|
25
|
+
)
|
26
|
+
from django_cfg.modules.django_admin.utils.badges import StatusBadge
|
24
27
|
from django_cfg.modules.django_logger import get_logger
|
25
28
|
|
29
|
+
from ..models import UniversalPayment
|
30
|
+
|
26
31
|
logger = get_logger("payments_admin")
|
27
32
|
|
28
33
|
|
29
34
|
@admin.register(UniversalPayment)
|
30
|
-
class UniversalPaymentAdmin(ModelAdmin):
|
35
|
+
class UniversalPaymentAdmin(OptimizedModelAdmin, DisplayMixin, ModelAdmin):
|
31
36
|
"""
|
32
|
-
|
37
|
+
UniversalPayment admin using Django Admin Utilities.
|
33
38
|
|
34
39
|
Features:
|
35
|
-
-
|
36
|
-
-
|
37
|
-
-
|
38
|
-
-
|
39
|
-
- Financial statistics and monitoring
|
40
|
+
- Clean display utilities with no HTML duplication
|
41
|
+
- Automatic query optimization
|
42
|
+
- Type-safe configuration
|
43
|
+
- Payment-specific status mapping
|
40
44
|
"""
|
41
45
|
|
42
|
-
#
|
43
|
-
|
46
|
+
# Performance optimization
|
47
|
+
select_related_fields = ['user', 'currency']
|
48
|
+
annotations = {}
|
49
|
+
# Note: Annotations should use Django expressions like F(), Case(), etc.
|
50
|
+
# Example: 'age_days': timezone.now() - F('created_at')
|
44
51
|
|
52
|
+
# List configuration
|
45
53
|
list_display = [
|
46
54
|
'payment_id_display',
|
47
55
|
'user_display',
|
48
56
|
'amount_display',
|
49
57
|
'status_display',
|
50
58
|
'provider_display',
|
51
|
-
'
|
52
|
-
'
|
53
|
-
'created_at_display'
|
54
|
-
]
|
55
|
-
|
56
|
-
list_display_links = ['payment_id_display']
|
57
|
-
|
58
|
-
search_fields = [
|
59
|
-
'id',
|
60
|
-
'provider_payment_id',
|
61
|
-
'user__email',
|
62
|
-
'user__first_name',
|
63
|
-
'user__last_name',
|
64
|
-
'user__username',
|
65
|
-
'description'
|
59
|
+
'status_changed_display',
|
60
|
+
'created_display'
|
66
61
|
]
|
67
62
|
|
68
63
|
list_filter = [
|
69
|
-
|
70
|
-
PaymentAmountFilter,
|
71
|
-
UserEmailFilter,
|
72
|
-
RecentActivityFilter,
|
64
|
+
'status',
|
73
65
|
'provider',
|
74
66
|
'currency',
|
75
67
|
'created_at'
|
76
68
|
]
|
77
69
|
|
70
|
+
search_fields = [
|
71
|
+
'internal_payment_id',
|
72
|
+
'transaction_hash',
|
73
|
+
'user__username',
|
74
|
+
'user__email',
|
75
|
+
'pay_address'
|
76
|
+
]
|
77
|
+
|
78
78
|
readonly_fields = [
|
79
|
-
'
|
80
|
-
'provider_payment_id',
|
81
|
-
'payment_url',
|
79
|
+
'internal_payment_id',
|
82
80
|
'created_at',
|
83
81
|
'updated_at',
|
84
|
-
'
|
85
|
-
|
86
|
-
|
87
|
-
# Unfold actions
|
88
|
-
actions_list = [
|
89
|
-
'check_payment_status',
|
90
|
-
'cancel_selected_payments',
|
91
|
-
'mark_as_completed',
|
92
|
-
'export_payment_data'
|
93
|
-
]
|
94
|
-
|
95
|
-
fieldsets = [
|
96
|
-
('Payment Information', {
|
97
|
-
'fields': [
|
98
|
-
'id',
|
99
|
-
'user',
|
100
|
-
'amount_usd',
|
101
|
-
'currency',
|
102
|
-
'crypto_amount',
|
103
|
-
'description'
|
104
|
-
]
|
105
|
-
}),
|
106
|
-
('Provider Details', {
|
107
|
-
'fields': [
|
108
|
-
'provider',
|
109
|
-
'provider_payment_id',
|
110
|
-
'payment_url'
|
111
|
-
]
|
112
|
-
}),
|
113
|
-
('Status & Tracking', {
|
114
|
-
'fields': [
|
115
|
-
'status',
|
116
|
-
'error_code',
|
117
|
-
'error_message',
|
118
|
-
'expires_at'
|
119
|
-
]
|
120
|
-
}),
|
121
|
-
('URLs & Callbacks', {
|
122
|
-
'fields': [
|
123
|
-
'callback_url',
|
124
|
-
'cancel_url'
|
125
|
-
],
|
126
|
-
'classes': ['collapse']
|
127
|
-
}),
|
128
|
-
('Metadata', {
|
129
|
-
'fields': [
|
130
|
-
'metadata'
|
131
|
-
],
|
132
|
-
'classes': ['collapse']
|
133
|
-
}),
|
134
|
-
('Timestamps', {
|
135
|
-
'fields': [
|
136
|
-
'created_at',
|
137
|
-
'updated_at',
|
138
|
-
'completed_at'
|
139
|
-
],
|
140
|
-
'classes': ['collapse']
|
141
|
-
})
|
82
|
+
'status_changed_at',
|
83
|
+
'payment_details_display'
|
142
84
|
]
|
143
85
|
|
144
|
-
|
145
|
-
|
146
|
-
return super().get_queryset(request).select_related('user')
|
86
|
+
# Register actions
|
87
|
+
actions = ['mark_as_completed', 'mark_as_failed', 'cancel_payments']
|
147
88
|
|
148
|
-
|
89
|
+
# Display methods using utilities
|
90
|
+
@display(description="Payment ID")
|
149
91
|
def payment_id_display(self, obj):
|
150
|
-
"""
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
'title="Click to copy full ID: {}">{}</span>',
|
155
|
-
obj.id,
|
156
|
-
short_id
|
92
|
+
"""Payment ID display with badge."""
|
93
|
+
return StatusBadge.create(
|
94
|
+
text=obj.internal_payment_id[:12] + "...",
|
95
|
+
variant="info"
|
157
96
|
)
|
158
97
|
|
159
|
-
@display(description="User",
|
98
|
+
@display(description="User", header=True)
|
160
99
|
def user_display(self, obj):
|
161
|
-
"""
|
162
|
-
|
163
|
-
display_name = obj.user.get_full_name() or obj.user.username
|
164
|
-
return format_html(
|
165
|
-
'<div class="flex items-center space-x-2">'
|
166
|
-
'<div class="w-8 h-8 bg-blue-500 rounded-full flex items-center justify-center text-white text-xs font-bold">'
|
167
|
-
'{}'
|
168
|
-
'</div>'
|
169
|
-
'<div>'
|
170
|
-
'<div class="font-medium text-gray-900 dark:text-gray-100">{}</div>'
|
171
|
-
'<div class="text-xs text-gray-500">{}</div>'
|
172
|
-
'</div>'
|
173
|
-
'</div>',
|
174
|
-
display_name[0].upper() if display_name else 'U',
|
175
|
-
display_name,
|
176
|
-
obj.user.email
|
177
|
-
)
|
178
|
-
return format_html('<span class="text-gray-500">No user</span>')
|
100
|
+
"""User display with avatar."""
|
101
|
+
return self.display_user_with_avatar(obj, 'user')
|
179
102
|
|
180
|
-
@display(description="Amount"
|
103
|
+
@display(description="Amount")
|
181
104
|
def amount_display(self, obj):
|
182
|
-
"""
|
183
|
-
|
184
|
-
|
185
|
-
if obj.
|
186
|
-
|
187
|
-
return format_html(
|
188
|
-
'<div class="text-right">'
|
189
|
-
'<div class="font-bold text-green-600 dark:text-green-400">{}</div>'
|
190
|
-
'<div class="text-xs text-gray-500">{} {}</div>'
|
191
|
-
'</div>',
|
192
|
-
usd_amount,
|
193
|
-
crypto_display,
|
194
|
-
obj.currency.code
|
195
|
-
)
|
105
|
+
"""Amount display with currency."""
|
106
|
+
# Get currency code from currency relation or default to USD
|
107
|
+
currency_code = "USD"
|
108
|
+
if obj.currency:
|
109
|
+
currency_code = getattr(obj.currency, 'code', 'USD')
|
196
110
|
|
197
|
-
|
198
|
-
|
199
|
-
|
111
|
+
config = MoneyDisplayConfig(
|
112
|
+
currency=currency_code,
|
113
|
+
show_sign=False,
|
114
|
+
thousand_separator=True
|
200
115
|
)
|
116
|
+
|
117
|
+
return self.display_money_amount(obj, 'amount_usd', config)
|
201
118
|
|
202
|
-
@display(description="Status",
|
119
|
+
@display(description="Status", label=True)
|
203
120
|
def status_display(self, obj):
|
204
|
-
"""
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
121
|
+
"""Status display with payment-specific colors."""
|
122
|
+
# Payment-specific status mappings
|
123
|
+
payment_status_mappings = {
|
124
|
+
'pending': 'warning',
|
125
|
+
'processing': 'info',
|
126
|
+
'completed': 'success',
|
127
|
+
'failed': 'danger',
|
128
|
+
'cancelled': 'secondary',
|
129
|
+
'expired': 'danger',
|
130
|
+
'refunded': 'warning'
|
214
131
|
}
|
215
132
|
|
216
|
-
|
217
|
-
|
218
|
-
|
133
|
+
config = StatusBadgeConfig(
|
134
|
+
custom_mappings=payment_status_mappings,
|
135
|
+
show_icons=True
|
219
136
|
)
|
220
137
|
|
221
|
-
return
|
222
|
-
'<span class="inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium {}">'
|
223
|
-
'{} {}'
|
224
|
-
'</span>',
|
225
|
-
color_class,
|
226
|
-
icon,
|
227
|
-
label
|
228
|
-
)
|
138
|
+
return self.display_status_auto(obj, 'status', config)
|
229
139
|
|
230
|
-
@display(description="Provider"
|
140
|
+
@display(description="Provider")
|
231
141
|
def provider_display(self, obj):
|
232
|
-
"""
|
142
|
+
"""Provider display with badge and icons."""
|
143
|
+
# Provider-specific styling and icons
|
233
144
|
provider_config = {
|
234
|
-
'
|
235
|
-
'
|
236
|
-
'
|
145
|
+
'stripe': {'variant': 'primary', 'icon': Icons.CREDIT_CARD},
|
146
|
+
'paypal': {'variant': 'info', 'icon': Icons.ACCOUNT_BALANCE_WALLET},
|
147
|
+
'crypto': {'variant': 'warning', 'icon': Icons.CURRENCY_BITCOIN},
|
148
|
+
'bank': {'variant': 'success', 'icon': Icons.ACCOUNT_BALANCE},
|
237
149
|
}
|
238
150
|
|
239
|
-
|
151
|
+
config = provider_config.get(obj.provider.lower(), {'variant': 'secondary', 'icon': Icons.PAYMENT})
|
240
152
|
|
241
|
-
|
242
|
-
|
243
|
-
'
|
244
|
-
'<span class="text-sm font-medium">{}</span>'
|
245
|
-
'</span>',
|
246
|
-
icon,
|
247
|
-
name
|
153
|
+
badge_config = StatusBadgeConfig(
|
154
|
+
show_icons=True,
|
155
|
+
icon=config['icon']
|
248
156
|
)
|
249
|
-
|
250
|
-
@display(description="Currency", ordering='currency__code')
|
251
|
-
def currency_display(self, obj):
|
252
|
-
"""Display currency with type indicator."""
|
253
|
-
if obj.currency:
|
254
|
-
# Use currency type from model
|
255
|
-
is_crypto = obj.currency.currency_type == 'crypto'
|
256
|
-
|
257
|
-
icon = 'âŋ' if is_crypto else 'đ°'
|
258
|
-
|
259
|
-
return format_html(
|
260
|
-
'<span class="inline-flex items-center space-x-1">'
|
261
|
-
'<span>{}</span>'
|
262
|
-
'<span class="font-mono font-bold">{}</span>'
|
263
|
-
'</span>',
|
264
|
-
icon,
|
265
|
-
obj.currency.code
|
266
|
-
)
|
267
157
|
|
268
|
-
return
|
158
|
+
return StatusBadge.create(
|
159
|
+
text=obj.provider.title(),
|
160
|
+
variant=config['variant'],
|
161
|
+
config=badge_config
|
162
|
+
)
|
269
163
|
|
270
|
-
@display(description="
|
271
|
-
def
|
272
|
-
"""
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
# Check if expired
|
279
|
-
if obj.expires_at and now > obj.expires_at:
|
280
|
-
return format_html(
|
281
|
-
'<div class="text-red-500 text-xs">'
|
282
|
-
'â Expired<br>'
|
283
|
-
'<span class="text-gray-400">{}</span>'
|
284
|
-
'</div>',
|
285
|
-
naturaltime(obj.expires_at)
|
286
|
-
)
|
287
|
-
|
288
|
-
# Show time remaining if has expiry
|
289
|
-
if obj.expires_at:
|
290
|
-
time_remaining = obj.expires_at - now
|
291
|
-
if time_remaining.total_seconds() > 0:
|
292
|
-
return format_html(
|
293
|
-
'<div class="text-orange-500 text-xs">'
|
294
|
-
'â° {} left<br>'
|
295
|
-
'<span class="text-gray-400">Created {}</span>'
|
296
|
-
'</div>',
|
297
|
-
naturaltime(now + time_remaining),
|
298
|
-
naturaltime(obj.created_at)
|
299
|
-
)
|
300
|
-
|
301
|
-
# Default: show creation time
|
302
|
-
return format_html(
|
303
|
-
'<div class="text-gray-500 text-xs">'
|
304
|
-
'Created<br>'
|
305
|
-
'<span>{}</span>'
|
306
|
-
'</div>',
|
307
|
-
naturaltime(obj.created_at)
|
164
|
+
@display(description="Created")
|
165
|
+
def created_display(self, obj):
|
166
|
+
"""Created time display."""
|
167
|
+
return self.display_datetime_relative(
|
168
|
+
obj,
|
169
|
+
'created_at',
|
170
|
+
DateTimeDisplayConfig(show_relative=True, show_seconds=False)
|
308
171
|
)
|
309
172
|
|
310
|
-
@display(description="
|
311
|
-
def
|
312
|
-
"""
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
'
|
318
|
-
|
319
|
-
naturaltime(obj.created_at)
|
173
|
+
@display(description="Status Changed")
|
174
|
+
def status_changed_display(self, obj):
|
175
|
+
"""Status changed time display."""
|
176
|
+
if not obj.status_changed_at:
|
177
|
+
return "-"
|
178
|
+
return self.display_datetime_relative(
|
179
|
+
obj,
|
180
|
+
'status_changed_at',
|
181
|
+
DateTimeDisplayConfig(show_relative=True, show_seconds=False)
|
320
182
|
)
|
321
183
|
|
322
|
-
|
323
|
-
|
324
|
-
|
184
|
+
# Readonly field displays
|
185
|
+
def payment_details_display(self, obj):
|
186
|
+
"""Detailed payment information for detail view."""
|
187
|
+
if not obj.pk:
|
188
|
+
return "Save to see details"
|
325
189
|
|
326
|
-
|
327
|
-
|
328
|
-
total_payments = UniversalPayment.objects.count()
|
329
|
-
|
330
|
-
# Status distribution
|
331
|
-
status_stats = {}
|
332
|
-
for status in UniversalPayment.PaymentStatus:
|
333
|
-
count = UniversalPayment.objects.filter(status=status).count()
|
334
|
-
status_stats[status] = count
|
335
|
-
|
336
|
-
# Financial statistics
|
337
|
-
total_amount = UniversalPayment.objects.aggregate(
|
338
|
-
total=Sum('amount_usd')
|
339
|
-
)['total'] or 0
|
340
|
-
|
341
|
-
completed_amount = UniversalPayment.objects.filter(
|
342
|
-
status=UniversalPayment.PaymentStatus.COMPLETED
|
343
|
-
).aggregate(total=Sum('amount_usd'))['total'] or 0
|
344
|
-
|
345
|
-
# Recent activity (24 hours)
|
346
|
-
recent_threshold = timezone.now() - timedelta(hours=24)
|
347
|
-
recent_payments = UniversalPayment.objects.filter(
|
348
|
-
created_at__gte=recent_threshold
|
349
|
-
).count()
|
350
|
-
|
351
|
-
recent_amount = UniversalPayment.objects.filter(
|
352
|
-
created_at__gte=recent_threshold
|
353
|
-
).aggregate(total=Sum('amount_usd'))['total'] or 0
|
354
|
-
|
355
|
-
# Provider statistics
|
356
|
-
provider_stats = UniversalPayment.objects.values('provider').annotate(
|
357
|
-
count=Count('id'),
|
358
|
-
amount=Sum('amount_usd')
|
359
|
-
).order_by('-count')
|
360
|
-
|
361
|
-
# Success rate
|
362
|
-
completed_count = status_stats.get(UniversalPayment.PaymentStatus.COMPLETED, 0)
|
363
|
-
success_rate = (completed_count / total_payments * 100) if total_payments > 0 else 0
|
364
|
-
|
365
|
-
extra_context.update({
|
366
|
-
'payment_stats': {
|
367
|
-
'total_payments': total_payments,
|
368
|
-
'status_stats': status_stats,
|
369
|
-
'total_amount': total_amount,
|
370
|
-
'completed_amount': completed_amount,
|
371
|
-
'recent_payments': recent_payments,
|
372
|
-
'recent_amount': recent_amount,
|
373
|
-
'provider_stats': provider_stats,
|
374
|
-
'success_rate': success_rate,
|
375
|
-
}
|
376
|
-
})
|
377
|
-
|
378
|
-
except Exception as e:
|
379
|
-
logger.warning(f"Failed to generate payment statistics: {e}")
|
380
|
-
extra_context['payment_stats'] = None
|
190
|
+
from django.utils.html import format_html
|
191
|
+
from django_cfg.modules.django_admin.utils.displays import MoneyDisplay
|
381
192
|
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
@action(
|
387
|
-
description="đ Check Payment Status",
|
388
|
-
icon="refresh",
|
389
|
-
variant=ActionVariant.INFO
|
390
|
-
)
|
391
|
-
def check_payment_status(self, request, queryset):
|
392
|
-
"""Check payment status with providers."""
|
193
|
+
# Calculate age
|
194
|
+
age = timezone.now() - obj.created_at
|
195
|
+
age_text = f"{age.days} days, {age.seconds // 3600} hours"
|
393
196
|
|
394
|
-
|
395
|
-
|
197
|
+
# Build details HTML
|
198
|
+
details = []
|
396
199
|
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
from ..services.core.payment_service import PaymentService
|
401
|
-
|
402
|
-
service = PaymentService()
|
403
|
-
result = service.check_payment_status(payment.id)
|
404
|
-
|
405
|
-
if result.success:
|
406
|
-
updated_count += 1
|
407
|
-
else:
|
408
|
-
error_count += 1
|
409
|
-
|
410
|
-
except Exception as e:
|
411
|
-
error_count += 1
|
412
|
-
logger.error(f"Failed to check payment status for {payment.id}: {e}")
|
200
|
+
# Basic info
|
201
|
+
details.append(f"<strong>Internal ID:</strong> {obj.internal_payment_id}")
|
202
|
+
details.append(f"<strong>Age:</strong> {age_text}")
|
413
203
|
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
f"â
Checked status for {updated_count} payments"
|
418
|
-
)
|
204
|
+
# Transaction details
|
205
|
+
if obj.transaction_hash:
|
206
|
+
details.append(f"<strong>Transaction Hash:</strong> {obj.transaction_hash}")
|
419
207
|
|
420
|
-
if
|
421
|
-
|
422
|
-
|
423
|
-
|
208
|
+
if obj.pay_address:
|
209
|
+
details.append(f"<strong>Pay Address:</strong> {obj.pay_address}")
|
210
|
+
|
211
|
+
if obj.pay_amount:
|
212
|
+
pay_amount_html = MoneyDisplay.amount(
|
213
|
+
obj.pay_amount,
|
214
|
+
MoneyDisplayConfig(currency="USD")
|
424
215
|
)
|
425
|
-
|
426
|
-
@action(
|
427
|
-
description="đĢ Cancel Selected Payments",
|
428
|
-
icon="cancel",
|
429
|
-
variant=ActionVariant.WARNING
|
430
|
-
)
|
431
|
-
def cancel_selected_payments(self, request, queryset):
|
432
|
-
"""Cancel selected payments."""
|
216
|
+
details.append(f"<strong>Pay Amount:</strong> {pay_amount_html}")
|
433
217
|
|
434
|
-
#
|
435
|
-
|
436
|
-
|
437
|
-
UniversalPayment.PaymentStatus.PENDING,
|
438
|
-
UniversalPayment.PaymentStatus.WAITING_FOR_PAYMENT
|
439
|
-
]
|
440
|
-
)
|
218
|
+
# URLs
|
219
|
+
if obj.callback_url:
|
220
|
+
details.append(f"<strong>Callback URL:</strong> {obj.callback_url}")
|
441
221
|
|
442
|
-
|
222
|
+
if obj.cancel_url:
|
223
|
+
details.append(f"<strong>Cancel URL:</strong> {obj.cancel_url}")
|
443
224
|
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
cancelled_count += 1
|
448
|
-
|
449
|
-
except Exception as e:
|
450
|
-
logger.error(f"Failed to cancel payment {payment.id}: {e}")
|
225
|
+
# Description
|
226
|
+
if obj.description:
|
227
|
+
details.append(f"<strong>Description:</strong> {obj.description}")
|
451
228
|
|
452
|
-
|
453
|
-
messages.success(
|
454
|
-
request,
|
455
|
-
f"đĢ Cancelled {cancelled_count} payments"
|
456
|
-
)
|
457
|
-
|
458
|
-
skipped_count = queryset.count() - cancelled_count
|
459
|
-
if skipped_count > 0:
|
460
|
-
messages.info(
|
461
|
-
request,
|
462
|
-
f"âšī¸ Skipped {skipped_count} payments (not cancelable)"
|
463
|
-
)
|
229
|
+
return format_html("<br>".join(details))
|
464
230
|
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
)
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
]
|
231
|
+
payment_details_display.short_description = "Payment Details"
|
232
|
+
|
233
|
+
# Actions
|
234
|
+
@action(description="Mark as completed", variant=ActionVariant.SUCCESS)
|
235
|
+
def mark_completed(self, request, queryset):
|
236
|
+
"""Mark selected payments as completed."""
|
237
|
+
updated = queryset.filter(
|
238
|
+
status__in=['pending', 'processing']
|
239
|
+
).update(status='completed')
|
240
|
+
|
241
|
+
self.message_user(
|
242
|
+
request,
|
243
|
+
f"Successfully marked {updated} payment(s) as completed.",
|
244
|
+
level='SUCCESS'
|
480
245
|
)
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
logger.error(f"Failed to complete payment {payment.id}: {e}")
|
491
|
-
|
492
|
-
if completed_count > 0:
|
493
|
-
messages.success(
|
494
|
-
request,
|
495
|
-
f"â
Marked {completed_count} payments as completed"
|
496
|
-
)
|
497
|
-
messages.warning(
|
498
|
-
request,
|
499
|
-
"â ī¸ Admin override used - ensure payments were actually received!"
|
500
|
-
)
|
501
|
-
|
502
|
-
skipped_count = queryset.count() - completed_count
|
503
|
-
if skipped_count > 0:
|
504
|
-
messages.info(
|
246
|
+
|
247
|
+
@action(description="Mark as failed", variant=ActionVariant.DANGER)
|
248
|
+
def mark_failed(self, request, queryset):
|
249
|
+
"""Mark selected payments as failed."""
|
250
|
+
updated = queryset.filter(
|
251
|
+
status__in=['pending', 'processing']
|
252
|
+
).update(status='failed')
|
253
|
+
|
254
|
+
self.message_user(
|
505
255
|
request,
|
506
|
-
|
507
|
-
|
256
|
+
f"Successfully marked {updated} payment(s) as failed.",
|
257
|
+
level='WARNING'
|
258
|
+
)
|
508
259
|
|
509
|
-
@action(
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
"""Export selected payments to CSV."""
|
516
|
-
|
517
|
-
import csv
|
518
|
-
from django.http import HttpResponse
|
519
|
-
|
520
|
-
response = HttpResponse(content_type='text/csv')
|
521
|
-
response['Content-Disposition'] = f'attachment; filename="payments_{timezone.now().strftime("%Y%m%d_%H%M%S")}.csv"'
|
522
|
-
|
523
|
-
writer = csv.writer(response)
|
524
|
-
writer.writerow([
|
525
|
-
'ID', 'User Email', 'Amount USD', 'Currency', 'Crypto Amount',
|
526
|
-
'Status', 'Provider', 'Created', 'Completed', 'Description'
|
527
|
-
])
|
528
|
-
|
529
|
-
for payment in queryset:
|
530
|
-
writer.writerow([
|
531
|
-
str(payment.id),
|
532
|
-
payment.user.email if payment.user else '',
|
533
|
-
payment.amount_usd,
|
534
|
-
payment.currency.code if payment.currency else '',
|
535
|
-
payment.amount_crypto or '',
|
536
|
-
payment.status,
|
537
|
-
payment.provider,
|
538
|
-
payment.created_at.isoformat(),
|
539
|
-
payment.completed_at.isoformat() if payment.completed_at else '',
|
540
|
-
payment.description or ''
|
541
|
-
])
|
260
|
+
@action(description="Cancel payments", variant=ActionVariant.WARNING)
|
261
|
+
def cancel_payments(self, request, queryset):
|
262
|
+
"""Cancel selected payments."""
|
263
|
+
updated = queryset.filter(
|
264
|
+
status__in=['pending', 'processing']
|
265
|
+
).update(status='cancelled')
|
542
266
|
|
543
|
-
|
267
|
+
self.message_user(
|
544
268
|
request,
|
545
|
-
f"
|
269
|
+
f"Successfully cancelled {updated} payment(s).",
|
270
|
+
level='WARNING'
|
546
271
|
)
|
547
|
-
|
548
|
-
return response
|