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.
Files changed (246) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/accounts/admin/__init__.py +24 -8
  3. django_cfg/apps/accounts/admin/activity_admin.py +146 -0
  4. django_cfg/apps/accounts/admin/filters.py +98 -22
  5. django_cfg/apps/accounts/admin/group_admin.py +86 -0
  6. django_cfg/apps/accounts/admin/inlines.py +42 -13
  7. django_cfg/apps/accounts/admin/otp_admin.py +115 -0
  8. django_cfg/apps/accounts/admin/registration_admin.py +173 -0
  9. django_cfg/apps/accounts/admin/resources.py +123 -19
  10. django_cfg/apps/accounts/admin/twilio_admin.py +327 -0
  11. django_cfg/apps/accounts/admin/user_admin.py +362 -0
  12. django_cfg/apps/agents/admin/__init__.py +17 -4
  13. django_cfg/apps/agents/admin/execution_admin.py +204 -183
  14. django_cfg/apps/agents/admin/registry_admin.py +230 -255
  15. django_cfg/apps/agents/admin/toolsets_admin.py +274 -321
  16. django_cfg/apps/agents/core/__init__.py +1 -1
  17. django_cfg/apps/agents/core/django_agent.py +221 -0
  18. django_cfg/apps/agents/core/exceptions.py +14 -0
  19. django_cfg/apps/agents/core/orchestrator.py +18 -3
  20. django_cfg/apps/knowbase/admin/__init__.py +1 -1
  21. django_cfg/apps/knowbase/admin/archive_admin.py +352 -640
  22. django_cfg/apps/knowbase/admin/chat_admin.py +258 -192
  23. django_cfg/apps/knowbase/admin/document_admin.py +269 -262
  24. django_cfg/apps/knowbase/admin/external_data_admin.py +271 -489
  25. django_cfg/apps/knowbase/config/settings.py +21 -4
  26. django_cfg/apps/knowbase/views/chat_views.py +3 -0
  27. django_cfg/apps/leads/admin/__init__.py +3 -1
  28. django_cfg/apps/leads/admin/leads_admin.py +235 -35
  29. django_cfg/apps/maintenance/admin/__init__.py +2 -2
  30. django_cfg/apps/maintenance/admin/api_key_admin.py +125 -63
  31. django_cfg/apps/maintenance/admin/log_admin.py +143 -61
  32. django_cfg/apps/maintenance/admin/scheduled_admin.py +212 -301
  33. django_cfg/apps/maintenance/admin/site_admin.py +213 -352
  34. django_cfg/apps/newsletter/admin/__init__.py +29 -2
  35. django_cfg/apps/newsletter/admin/newsletter_admin.py +531 -193
  36. django_cfg/apps/payments/admin/__init__.py +18 -27
  37. django_cfg/apps/payments/admin/api_keys_admin.py +179 -546
  38. django_cfg/apps/payments/admin/balance_admin.py +166 -632
  39. django_cfg/apps/payments/admin/currencies_admin.py +235 -607
  40. django_cfg/apps/payments/admin/endpoint_groups_admin.py +127 -0
  41. django_cfg/apps/payments/admin/filters.py +83 -3
  42. django_cfg/apps/payments/admin/networks_admin.py +269 -0
  43. django_cfg/apps/payments/admin/payments_admin.py +183 -460
  44. django_cfg/apps/payments/admin/subscriptions_admin.py +119 -636
  45. django_cfg/apps/payments/admin/tariffs_admin.py +248 -0
  46. django_cfg/apps/payments/admin_interface/serializers/payment_serializers.py +153 -34
  47. django_cfg/apps/payments/admin_interface/templates/payments/components/payment_card.html +121 -0
  48. django_cfg/apps/payments/admin_interface/templates/payments/components/payment_qr_code.html +95 -0
  49. django_cfg/apps/payments/admin_interface/templates/payments/components/progress_bar.html +37 -0
  50. django_cfg/apps/payments/admin_interface/templates/payments/components/provider_stats.html +60 -0
  51. django_cfg/apps/payments/admin_interface/templates/payments/components/status_badge.html +41 -0
  52. django_cfg/apps/payments/admin_interface/templates/payments/components/status_overview.html +83 -0
  53. django_cfg/apps/payments/admin_interface/templates/payments/payment_detail.html +363 -0
  54. django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +43 -17
  55. django_cfg/apps/payments/admin_interface/views/__init__.py +2 -0
  56. django_cfg/apps/payments/admin_interface/views/api/payments.py +102 -0
  57. django_cfg/apps/payments/admin_interface/views/api/webhook_admin.py +109 -63
  58. django_cfg/apps/payments/admin_interface/views/forms.py +5 -1
  59. django_cfg/apps/payments/config/__init__.py +14 -15
  60. django_cfg/apps/payments/config/django_cfg_integration.py +59 -1
  61. django_cfg/apps/payments/config/helpers.py +8 -13
  62. django_cfg/apps/payments/management/commands/manage_currencies.py +236 -274
  63. django_cfg/apps/payments/management/commands/manage_providers.py +4 -1
  64. django_cfg/apps/payments/middleware/api_access.py +32 -6
  65. django_cfg/apps/payments/migrations/0001_initial.py +33 -46
  66. django_cfg/apps/payments/migrations/0002_rename_payments_un_user_id_7f6e79_idx_payments_un_user_id_8ce187_idx_and_more.py +46 -0
  67. django_cfg/apps/payments/migrations/0003_universalpayment_status_changed_at.py +25 -0
  68. django_cfg/apps/payments/models/balance.py +12 -0
  69. django_cfg/apps/payments/models/currencies.py +106 -32
  70. django_cfg/apps/payments/models/managers/currency_managers.py +65 -0
  71. django_cfg/apps/payments/models/managers/payment_managers.py +142 -25
  72. django_cfg/apps/payments/models/payments.py +94 -0
  73. django_cfg/apps/payments/services/core/base.py +4 -4
  74. django_cfg/apps/payments/services/core/currency_service.py +35 -28
  75. django_cfg/apps/payments/services/core/payment_service.py +266 -39
  76. django_cfg/apps/payments/services/providers/__init__.py +3 -0
  77. django_cfg/apps/payments/services/providers/base.py +303 -41
  78. django_cfg/apps/payments/services/providers/models/__init__.py +42 -0
  79. django_cfg/apps/payments/services/providers/models/base.py +145 -0
  80. django_cfg/apps/payments/services/providers/models/providers.py +87 -0
  81. django_cfg/apps/payments/services/providers/models/universal.py +48 -0
  82. django_cfg/apps/payments/services/providers/nowpayments/__init__.py +31 -0
  83. django_cfg/apps/payments/services/providers/nowpayments/config.py +70 -0
  84. django_cfg/apps/payments/services/providers/nowpayments/models.py +150 -0
  85. django_cfg/apps/payments/services/providers/nowpayments/parsers.py +879 -0
  86. django_cfg/apps/payments/services/providers/nowpayments/provider.py +557 -0
  87. django_cfg/apps/payments/services/providers/nowpayments/sync.py +196 -0
  88. django_cfg/apps/payments/services/providers/registry.py +9 -37
  89. django_cfg/apps/payments/services/providers/sync_service.py +277 -0
  90. django_cfg/apps/payments/services/types/requests.py +19 -7
  91. django_cfg/apps/payments/signals/payment_signals.py +31 -2
  92. django_cfg/apps/payments/static/payments/js/api-client.js +29 -6
  93. django_cfg/apps/payments/static/payments/js/payment-detail.js +167 -0
  94. django_cfg/apps/payments/static/payments/js/payment-form.js +98 -32
  95. django_cfg/apps/payments/tasks/__init__.py +39 -0
  96. django_cfg/apps/payments/tasks/types.py +73 -0
  97. django_cfg/apps/payments/tasks/usage_tracking.py +308 -0
  98. django_cfg/apps/payments/templates/admin/payments/_components/dashboard_header.html +23 -0
  99. django_cfg/apps/payments/templates/admin/payments/_components/stats_card.html +25 -0
  100. django_cfg/apps/payments/templates/admin/payments/_components/stats_grid.html +16 -0
  101. django_cfg/apps/payments/templates/admin/payments/apikey/change_list.html +39 -0
  102. django_cfg/apps/payments/templates/admin/payments/balance/change_list.html +50 -0
  103. django_cfg/apps/payments/templates/admin/payments/currency/change_list.html +40 -0
  104. django_cfg/apps/payments/templates/admin/payments/payment/change_list.html +48 -0
  105. django_cfg/apps/payments/templates/admin/payments/subscription/change_list.html +48 -0
  106. django_cfg/apps/payments/templatetags/payment_tags.py +8 -0
  107. django_cfg/apps/payments/urls.py +3 -2
  108. django_cfg/apps/payments/urls_admin.py +1 -1
  109. django_cfg/apps/payments/views/api/currencies.py +8 -5
  110. django_cfg/apps/payments/views/overview/services.py +2 -2
  111. django_cfg/apps/payments/views/serializers/currencies.py +22 -8
  112. django_cfg/apps/support/admin/__init__.py +10 -1
  113. django_cfg/apps/support/admin/support_admin.py +338 -141
  114. django_cfg/apps/tasks/admin/__init__.py +11 -0
  115. django_cfg/apps/tasks/admin/tasks_admin.py +430 -0
  116. django_cfg/apps/tasks/static/tasks/css/dashboard.css +68 -217
  117. django_cfg/apps/tasks/static/tasks/js/api.js +40 -84
  118. django_cfg/apps/tasks/static/tasks/js/components/DataManager.js +24 -0
  119. django_cfg/apps/tasks/static/tasks/js/components/TabManager.js +85 -0
  120. django_cfg/apps/tasks/static/tasks/js/components/TaskRenderer.js +216 -0
  121. django_cfg/apps/tasks/static/tasks/js/dashboard/main.mjs +245 -0
  122. django_cfg/apps/tasks/static/tasks/js/dashboard/overview.mjs +123 -0
  123. django_cfg/apps/tasks/static/tasks/js/dashboard/queues.mjs +120 -0
  124. django_cfg/apps/tasks/static/tasks/js/dashboard/tasks.mjs +350 -0
  125. django_cfg/apps/tasks/static/tasks/js/dashboard/workers.mjs +169 -0
  126. django_cfg/apps/tasks/tasks/__init__.py +10 -0
  127. django_cfg/apps/tasks/tasks/demo_tasks.py +133 -0
  128. django_cfg/apps/tasks/templates/tasks/components/management_actions.html +42 -45
  129. django_cfg/apps/tasks/templates/tasks/components/{status_cards.html → overview_content.html} +30 -11
  130. django_cfg/apps/tasks/templates/tasks/components/queues_content.html +19 -0
  131. django_cfg/apps/tasks/templates/tasks/components/tab_navigation.html +16 -10
  132. django_cfg/apps/tasks/templates/tasks/components/tasks_content.html +51 -0
  133. django_cfg/apps/tasks/templates/tasks/components/workers_content.html +30 -0
  134. django_cfg/apps/tasks/templates/tasks/layout/base.html +117 -0
  135. django_cfg/apps/tasks/templates/tasks/pages/dashboard.html +82 -0
  136. django_cfg/apps/tasks/templates/tasks/partials/task_row_template.html +40 -0
  137. django_cfg/apps/tasks/templates/tasks/widgets/task_filters.html +37 -0
  138. django_cfg/apps/tasks/templates/tasks/widgets/task_footer.html +41 -0
  139. django_cfg/apps/tasks/templates/tasks/widgets/task_table.html +50 -0
  140. django_cfg/apps/tasks/urls.py +2 -2
  141. django_cfg/apps/tasks/urls_admin.py +2 -2
  142. django_cfg/apps/tasks/utils/__init__.py +1 -0
  143. django_cfg/apps/tasks/utils/simulator.py +356 -0
  144. django_cfg/apps/tasks/views/__init__.py +16 -0
  145. django_cfg/apps/tasks/views/api.py +569 -0
  146. django_cfg/apps/tasks/views/dashboard.py +58 -0
  147. django_cfg/config.py +1 -1
  148. django_cfg/core/config.py +10 -5
  149. django_cfg/core/generation.py +1 -1
  150. django_cfg/core/integration/__init__.py +21 -0
  151. django_cfg/management/commands/__init__.py +13 -1
  152. django_cfg/management/commands/migrate_all.py +9 -3
  153. django_cfg/management/commands/migrator.py +11 -6
  154. django_cfg/management/commands/rundramatiq.py +3 -2
  155. django_cfg/management/commands/rundramatiq_simulator.py +430 -0
  156. django_cfg/middleware/__init__.py +0 -2
  157. django_cfg/models/api_keys.py +115 -0
  158. django_cfg/models/constance.py +0 -11
  159. django_cfg/models/payments.py +137 -3
  160. django_cfg/modules/django_admin/__init__.py +64 -0
  161. django_cfg/modules/django_admin/decorators/__init__.py +13 -0
  162. django_cfg/modules/django_admin/decorators/actions.py +106 -0
  163. django_cfg/modules/django_admin/decorators/display.py +106 -0
  164. django_cfg/modules/django_admin/mixins/__init__.py +14 -0
  165. django_cfg/modules/django_admin/mixins/display_mixin.py +81 -0
  166. django_cfg/modules/django_admin/mixins/optimization_mixin.py +41 -0
  167. django_cfg/modules/django_admin/mixins/standalone_actions_mixin.py +202 -0
  168. django_cfg/modules/django_admin/models/__init__.py +20 -0
  169. django_cfg/modules/django_admin/models/action_models.py +33 -0
  170. django_cfg/modules/django_admin/models/badge_models.py +20 -0
  171. django_cfg/modules/django_admin/models/base.py +26 -0
  172. django_cfg/modules/django_admin/models/display_models.py +31 -0
  173. django_cfg/modules/django_admin/utils/badges.py +159 -0
  174. django_cfg/modules/django_admin/utils/displays.py +247 -0
  175. django_cfg/modules/django_currency/__init__.py +2 -2
  176. django_cfg/modules/django_currency/clients/__init__.py +2 -2
  177. django_cfg/modules/django_currency/clients/hybrid_client.py +587 -0
  178. django_cfg/modules/django_currency/core/converter.py +12 -12
  179. django_cfg/modules/django_currency/database/__init__.py +2 -2
  180. django_cfg/modules/django_currency/database/database_loader.py +93 -42
  181. django_cfg/modules/django_llm/llm/client.py +10 -2
  182. django_cfg/modules/django_tasks.py +54 -21
  183. django_cfg/modules/django_unfold/callbacks/actions.py +1 -1
  184. django_cfg/modules/django_unfold/callbacks/statistics.py +1 -1
  185. django_cfg/modules/django_unfold/dashboard.py +14 -13
  186. django_cfg/modules/django_unfold/models/config.py +1 -1
  187. django_cfg/registry/core.py +7 -9
  188. django_cfg/registry/third_party.py +2 -2
  189. django_cfg/template_archive/django_sample.zip +0 -0
  190. {django_cfg-1.3.7.dist-info → django_cfg-1.3.11.dist-info}/METADATA +2 -1
  191. {django_cfg-1.3.7.dist-info → django_cfg-1.3.11.dist-info}/RECORD +198 -160
  192. django_cfg/apps/accounts/admin/activity.py +0 -96
  193. django_cfg/apps/accounts/admin/group.py +0 -17
  194. django_cfg/apps/accounts/admin/otp.py +0 -59
  195. django_cfg/apps/accounts/admin/registration_source.py +0 -97
  196. django_cfg/apps/accounts/admin/twilio_response.py +0 -227
  197. django_cfg/apps/accounts/admin/user.py +0 -300
  198. django_cfg/apps/agents/core/agent.py +0 -281
  199. django_cfg/apps/payments/admin_interface/old/payments/base.html +0 -175
  200. django_cfg/apps/payments/admin_interface/old/payments/components/dev_tool_card.html +0 -125
  201. django_cfg/apps/payments/admin_interface/old/payments/components/loading_spinner.html +0 -16
  202. django_cfg/apps/payments/admin_interface/old/payments/components/ngrok_status_card.html +0 -113
  203. django_cfg/apps/payments/admin_interface/old/payments/components/notification.html +0 -27
  204. django_cfg/apps/payments/admin_interface/old/payments/components/provider_card.html +0 -86
  205. django_cfg/apps/payments/admin_interface/old/payments/components/status_card.html +0 -35
  206. django_cfg/apps/payments/admin_interface/old/payments/currency_converter.html +0 -382
  207. django_cfg/apps/payments/admin_interface/old/payments/payment_dashboard.html +0 -309
  208. django_cfg/apps/payments/admin_interface/old/payments/payment_form.html +0 -303
  209. django_cfg/apps/payments/admin_interface/old/payments/payment_list.html +0 -382
  210. django_cfg/apps/payments/admin_interface/old/payments/payment_status.html +0 -500
  211. django_cfg/apps/payments/admin_interface/old/payments/webhook_dashboard.html +0 -518
  212. django_cfg/apps/payments/admin_interface/old/static/payments/css/components.css +0 -619
  213. django_cfg/apps/payments/admin_interface/old/static/payments/css/dashboard.css +0 -188
  214. django_cfg/apps/payments/admin_interface/old/static/payments/js/components.js +0 -545
  215. django_cfg/apps/payments/admin_interface/old/static/payments/js/ngrok-status.js +0 -163
  216. django_cfg/apps/payments/admin_interface/old/static/payments/js/utils.js +0 -412
  217. django_cfg/apps/payments/config/constance/__init__.py +0 -22
  218. django_cfg/apps/payments/config/constance/config_service.py +0 -123
  219. django_cfg/apps/payments/config/constance/fields.py +0 -69
  220. django_cfg/apps/payments/config/constance/settings.py +0 -160
  221. django_cfg/apps/payments/services/providers/nowpayments.py +0 -478
  222. django_cfg/apps/tasks/admin.py +0 -320
  223. django_cfg/apps/tasks/static/tasks/js/dashboard.js +0 -614
  224. django_cfg/apps/tasks/static/tasks/js/modals.js +0 -452
  225. django_cfg/apps/tasks/static/tasks/js/notifications.js +0 -144
  226. django_cfg/apps/tasks/static/tasks/js/task-monitor.js +0 -454
  227. django_cfg/apps/tasks/static/tasks/js/theme.js +0 -77
  228. django_cfg/apps/tasks/templates/tasks/base.html +0 -96
  229. django_cfg/apps/tasks/templates/tasks/components/info_cards.html +0 -85
  230. django_cfg/apps/tasks/templates/tasks/components/overview_tab.html +0 -22
  231. django_cfg/apps/tasks/templates/tasks/components/queues_tab.html +0 -19
  232. django_cfg/apps/tasks/templates/tasks/components/task_details_modal.html +0 -103
  233. django_cfg/apps/tasks/templates/tasks/components/tasks_tab.html +0 -32
  234. django_cfg/apps/tasks/templates/tasks/components/workers_tab.html +0 -29
  235. django_cfg/apps/tasks/templates/tasks/dashboard.html +0 -29
  236. django_cfg/apps/tasks/views.py +0 -461
  237. django_cfg/management/commands/auto_generate.py +0 -486
  238. django_cfg/middleware/static_nocache.py +0 -55
  239. django_cfg/modules/django_currency/clients/yahoo_client.py +0 -157
  240. /django_cfg/modules/{django_unfold → django_admin}/icons/README.md +0 -0
  241. /django_cfg/modules/{django_unfold → django_admin}/icons/__init__.py +0 -0
  242. /django_cfg/modules/{django_unfold → django_admin}/icons/constants.py +0 -0
  243. /django_cfg/modules/{django_unfold → django_admin}/icons/generate_icons.py +0 -0
  244. {django_cfg-1.3.7.dist-info → django_cfg-1.3.11.dist-info}/WHEEL +0 -0
  245. {django_cfg-1.3.7.dist-info → django_cfg-1.3.11.dist-info}/entry_points.txt +0 -0
  246. {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 with Unfold integration.
2
+ Payment Admin interface using Django Admin Utilities.
3
3
 
4
- Advanced payment management with filtering, actions, and monitoring.
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 ..models import UniversalPayment
23
- from .filters import PaymentStatusFilter, PaymentAmountFilter, UserEmailFilter, RecentActivityFilter
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
- Advanced Payment admin with Unfold styling and comprehensive management features.
37
+ UniversalPayment admin using Django Admin Utilities.
33
38
 
34
39
  Features:
35
- - Real-time status tracking with visual indicators
36
- - Advanced filtering and search capabilities
37
- - Bulk operations for payment management
38
- - Provider-specific information display
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
- # Custom template for payment statistics
43
- change_list_template = 'admin/payments/payment/change_list.html'
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
- 'currency_display',
52
- 'progress_display',
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
- PaymentStatusFilter,
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
- 'id',
80
- 'provider_payment_id',
81
- 'payment_url',
79
+ 'internal_payment_id',
82
80
  'created_at',
83
81
  'updated_at',
84
- 'completed_at'
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
- def get_queryset(self, request):
145
- """Optimize queryset with user data."""
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
- @display(description="Payment ID", ordering='id')
89
+ # Display methods using utilities
90
+ @display(description="Payment ID")
149
91
  def payment_id_display(self, obj):
150
- """Display payment ID with copy functionality."""
151
- short_id = str(obj.id)[:8]
152
- return format_html(
153
- '<span class="font-mono text-sm bg-gray-100 dark:bg-gray-800 px-2 py-1 rounded" '
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", ordering='user__email')
98
+ @display(description="User", header=True)
160
99
  def user_display(self, obj):
161
- """Display user information with avatar."""
162
- if obj.user:
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", ordering='amount_usd')
103
+ @display(description="Amount")
181
104
  def amount_display(self, obj):
182
- """Display amount with currency conversion."""
183
- usd_amount = f"${obj.amount_usd:,.2f}"
184
-
185
- if obj.amount_crypto and obj.currency:
186
- crypto_display = f"{obj.amount_crypto:.8f}".rstrip('0').rstrip('.')
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
- return format_html(
198
- '<div class="text-right font-bold text-green-600 dark:text-green-400">{}</div>',
199
- usd_amount
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", ordering='status')
119
+ @display(description="Status", label=True)
203
120
  def status_display(self, obj):
204
- """Display status with colored badge and icon."""
205
- status_config = {
206
- UniversalPayment.PaymentStatus.PENDING: ('🟡', 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200', 'Pending'),
207
- UniversalPayment.PaymentStatus.WAITING_FOR_PAYMENT: ('⏰', 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200', 'Waiting'),
208
- UniversalPayment.PaymentStatus.CONFIRMING: ('🔄', 'bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-200', 'Confirming'),
209
- UniversalPayment.PaymentStatus.COMPLETED: ('✅', 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200', 'Completed'),
210
- UniversalPayment.PaymentStatus.FAILED: ('❌', 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200', 'Failed'),
211
- UniversalPayment.PaymentStatus.CANCELLED: ('đŸšĢ', 'bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-200', 'Cancelled'),
212
- UniversalPayment.PaymentStatus.EXPIRED: ('⌛', 'bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-200', 'Expired'),
213
- UniversalPayment.PaymentStatus.REFUNDED: ('â†Šī¸', 'bg-indigo-100 text-indigo-800 dark:bg-indigo-900 dark:text-indigo-200', 'Refunded'),
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
- icon, color_class, label = status_config.get(
217
- obj.status,
218
- ('❓', 'bg-gray-100 text-gray-800', 'Unknown')
133
+ config = StatusBadgeConfig(
134
+ custom_mappings=payment_status_mappings,
135
+ show_icons=True
219
136
  )
220
137
 
221
- return format_html(
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", ordering='provider')
140
+ @display(description="Provider")
231
141
  def provider_display(self, obj):
232
- """Display provider with logo/icon."""
142
+ """Provider display with badge and icons."""
143
+ # Provider-specific styling and icons
233
144
  provider_config = {
234
- 'nowpayments': ('đŸŸĻ', 'NowPayments'),
235
- 'cryptomus': ('🟩', 'Cryptomus'),
236
- 'cryptapi': ('đŸŸĒ', 'CryptAPI'),
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
- icon, name = provider_config.get(obj.provider, ('🔷', obj.provider.title()))
151
+ config = provider_config.get(obj.provider.lower(), {'variant': 'secondary', 'icon': Icons.PAYMENT})
240
152
 
241
- return format_html(
242
- '<span class="inline-flex items-center space-x-1">'
243
- '<span>{}</span>'
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 format_html('<span class="text-gray-500">—</span>')
158
+ return StatusBadge.create(
159
+ text=obj.provider.title(),
160
+ variant=config['variant'],
161
+ config=badge_config
162
+ )
269
163
 
270
- @display(description="Progress")
271
- def progress_display(self, obj):
272
- """Display payment progress with time information."""
273
- now = timezone.now()
274
-
275
- # Calculate time since creation
276
- time_elapsed = now - obj.created_at
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="Created", ordering='created_at')
311
- def created_at_display(self, obj):
312
- """Display creation date with relative time."""
313
- return format_html(
314
- '<div class="text-xs">'
315
- '<div class="font-medium">{}</div>'
316
- '<div class="text-gray-500">{}</div>'
317
- '</div>',
318
- obj.created_at.strftime('%Y-%m-%d %H:%M'),
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
- def changelist_view(self, request, extra_context=None):
323
- """Add payment statistics to changelist context."""
324
- extra_context = extra_context or {}
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
- try:
327
- # Basic statistics
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
- return super().changelist_view(request, extra_context)
383
-
384
- # ===== ADMIN ACTIONS =====
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
- updated_count = 0
395
- error_count = 0
197
+ # Build details HTML
198
+ details = []
396
199
 
397
- for payment in queryset:
398
- try:
399
- # Use payment service to check status
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
- if updated_count > 0:
415
- messages.success(
416
- request,
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 error_count > 0:
421
- messages.warning(
422
- request,
423
- f"âš ī¸ Failed to check {error_count} payments"
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
- # Only allow cancellation of pending/waiting payments
435
- cancelable_payments = queryset.filter(
436
- status__in=[
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
- cancelled_count = 0
222
+ if obj.cancel_url:
223
+ details.append(f"<strong>Cancel URL:</strong> {obj.cancel_url}")
443
224
 
444
- for payment in cancelable_payments:
445
- try:
446
- payment.mark_cancelled(reason="Cancelled by admin")
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
- if cancelled_count > 0:
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
- @action(
466
- description="✅ Mark as Completed",
467
- icon="check_circle",
468
- variant=ActionVariant.SUCCESS
469
- )
470
- def mark_as_completed(self, request, queryset):
471
- """Mark selected payments as completed (admin override)."""
472
-
473
- # Only allow completion of pending/waiting/confirming payments
474
- completable_payments = queryset.filter(
475
- status__in=[
476
- UniversalPayment.PaymentStatus.PENDING,
477
- UniversalPayment.PaymentStatus.WAITING_FOR_PAYMENT,
478
- UniversalPayment.PaymentStatus.CONFIRMING
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
- completed_count = 0
483
-
484
- for payment in completable_payments:
485
- try:
486
- payment.mark_completed()
487
- completed_count += 1
488
-
489
- except Exception as e:
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
- f"â„šī¸ Skipped {skipped_count} payments (not completable)"
507
- )
256
+ f"Successfully marked {updated} payment(s) as failed.",
257
+ level='WARNING'
258
+ )
508
259
 
509
- @action(
510
- description="📊 Export Payment Data",
511
- icon="download",
512
- variant=ActionVariant.INFO
513
- )
514
- def export_payment_data(self, request, queryset):
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
- messages.success(
267
+ self.message_user(
544
268
  request,
545
- f"📊 Exported {queryset.count()} payments to CSV"
269
+ f"Successfully cancelled {updated} payment(s).",
270
+ level='WARNING'
546
271
  )
547
-
548
- return response