django-cfg 1.3.5__py3-none-any.whl → 1.3.9__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (252) 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 +258 -0
  43. django_cfg/apps/payments/admin/payments_admin.py +171 -461
  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 +105 -34
  47. django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +12 -16
  48. django_cfg/apps/payments/admin_interface/views/__init__.py +2 -0
  49. django_cfg/apps/payments/admin_interface/views/api/webhook_admin.py +13 -18
  50. django_cfg/apps/payments/management/commands/manage_currencies.py +236 -274
  51. django_cfg/apps/payments/management/commands/manage_providers.py +4 -1
  52. django_cfg/apps/payments/middleware/api_access.py +32 -6
  53. django_cfg/apps/payments/migrations/0002_currency_usd_rate_currency_usd_rate_updated_at.py +26 -0
  54. django_cfg/apps/payments/migrations/0003_remove_provider_currency_fields.py +28 -0
  55. django_cfg/apps/payments/migrations/0004_add_reserved_usd_field.py +30 -0
  56. django_cfg/apps/payments/models/balance.py +12 -0
  57. django_cfg/apps/payments/models/currencies.py +106 -32
  58. django_cfg/apps/payments/models/managers/currency_managers.py +65 -0
  59. django_cfg/apps/payments/services/core/currency_service.py +35 -28
  60. django_cfg/apps/payments/services/core/payment_service.py +1 -1
  61. django_cfg/apps/payments/services/providers/__init__.py +3 -0
  62. django_cfg/apps/payments/services/providers/base.py +95 -39
  63. django_cfg/apps/payments/services/providers/models/__init__.py +40 -0
  64. django_cfg/apps/payments/services/providers/models/base.py +122 -0
  65. django_cfg/apps/payments/services/providers/models/providers.py +87 -0
  66. django_cfg/apps/payments/services/providers/models/universal.py +48 -0
  67. django_cfg/apps/payments/services/providers/nowpayments/__init__.py +31 -0
  68. django_cfg/apps/payments/services/providers/nowpayments/config.py +70 -0
  69. django_cfg/apps/payments/services/providers/nowpayments/models.py +150 -0
  70. django_cfg/apps/payments/services/providers/nowpayments/parsers.py +879 -0
  71. django_cfg/apps/payments/services/providers/{nowpayments.py → nowpayments/provider.py} +240 -209
  72. django_cfg/apps/payments/services/providers/nowpayments/sync.py +196 -0
  73. django_cfg/apps/payments/services/providers/registry.py +4 -32
  74. django_cfg/apps/payments/services/providers/sync_service.py +277 -0
  75. django_cfg/apps/payments/static/payments/js/api-client.js +23 -5
  76. django_cfg/apps/payments/static/payments/js/payment-form.js +65 -8
  77. django_cfg/apps/payments/tasks/__init__.py +39 -0
  78. django_cfg/apps/payments/tasks/types.py +73 -0
  79. django_cfg/apps/payments/tasks/usage_tracking.py +308 -0
  80. django_cfg/apps/payments/templates/admin/payments/_components/dashboard_header.html +23 -0
  81. django_cfg/apps/payments/templates/admin/payments/_components/stats_card.html +25 -0
  82. django_cfg/apps/payments/templates/admin/payments/_components/stats_grid.html +16 -0
  83. django_cfg/apps/payments/templates/admin/payments/apikey/change_list.html +39 -0
  84. django_cfg/apps/payments/templates/admin/payments/balance/change_list.html +50 -0
  85. django_cfg/apps/payments/templates/admin/payments/currency/change_list.html +40 -0
  86. django_cfg/apps/payments/templates/admin/payments/payment/change_list.html +48 -0
  87. django_cfg/apps/payments/templates/admin/payments/subscription/change_list.html +48 -0
  88. django_cfg/apps/payments/urls_admin.py +1 -1
  89. django_cfg/apps/payments/views/api/currencies.py +5 -5
  90. django_cfg/apps/payments/views/overview/services.py +2 -2
  91. django_cfg/apps/payments/views/serializers/currencies.py +4 -3
  92. django_cfg/apps/support/admin/__init__.py +10 -1
  93. django_cfg/apps/support/admin/support_admin.py +338 -141
  94. django_cfg/apps/tasks/admin/__init__.py +11 -0
  95. django_cfg/apps/tasks/admin/tasks_admin.py +430 -0
  96. django_cfg/apps/urls.py +1 -2
  97. django_cfg/config.py +1 -1
  98. django_cfg/core/config.py +10 -5
  99. django_cfg/core/generation.py +1 -1
  100. django_cfg/management/commands/__init__.py +13 -1
  101. django_cfg/management/commands/app_agent_diagnose.py +470 -0
  102. django_cfg/management/commands/app_agent_generate.py +342 -0
  103. django_cfg/management/commands/app_agent_info.py +308 -0
  104. django_cfg/management/commands/migrate_all.py +9 -3
  105. django_cfg/management/commands/migrator.py +11 -6
  106. django_cfg/management/commands/rundramatiq.py +3 -2
  107. django_cfg/middleware/__init__.py +0 -2
  108. django_cfg/models/api_keys.py +115 -0
  109. django_cfg/modules/django_admin/__init__.py +64 -0
  110. django_cfg/modules/django_admin/decorators/__init__.py +13 -0
  111. django_cfg/modules/django_admin/decorators/actions.py +106 -0
  112. django_cfg/modules/django_admin/decorators/display.py +106 -0
  113. django_cfg/modules/django_admin/mixins/__init__.py +14 -0
  114. django_cfg/modules/django_admin/mixins/display_mixin.py +81 -0
  115. django_cfg/modules/django_admin/mixins/optimization_mixin.py +41 -0
  116. django_cfg/modules/django_admin/mixins/standalone_actions_mixin.py +202 -0
  117. django_cfg/modules/django_admin/models/__init__.py +20 -0
  118. django_cfg/modules/django_admin/models/action_models.py +33 -0
  119. django_cfg/modules/django_admin/models/badge_models.py +20 -0
  120. django_cfg/modules/django_admin/models/base.py +26 -0
  121. django_cfg/modules/django_admin/models/display_models.py +31 -0
  122. django_cfg/modules/django_admin/utils/badges.py +159 -0
  123. django_cfg/modules/django_admin/utils/displays.py +247 -0
  124. django_cfg/modules/django_app_agent/__init__.py +87 -0
  125. django_cfg/modules/django_app_agent/agents/__init__.py +40 -0
  126. django_cfg/modules/django_app_agent/agents/base/__init__.py +24 -0
  127. django_cfg/modules/django_app_agent/agents/base/agent.py +354 -0
  128. django_cfg/modules/django_app_agent/agents/base/context.py +236 -0
  129. django_cfg/modules/django_app_agent/agents/base/executor.py +430 -0
  130. django_cfg/modules/django_app_agent/agents/generation/__init__.py +12 -0
  131. django_cfg/modules/django_app_agent/agents/generation/app_generator/__init__.py +15 -0
  132. django_cfg/modules/django_app_agent/agents/generation/app_generator/config_validator.py +147 -0
  133. django_cfg/modules/django_app_agent/agents/generation/app_generator/main.py +99 -0
  134. django_cfg/modules/django_app_agent/agents/generation/app_generator/models.py +32 -0
  135. django_cfg/modules/django_app_agent/agents/generation/app_generator/prompt_manager.py +290 -0
  136. django_cfg/modules/django_app_agent/agents/interfaces.py +376 -0
  137. django_cfg/modules/django_app_agent/core/__init__.py +33 -0
  138. django_cfg/modules/django_app_agent/core/config.py +300 -0
  139. django_cfg/modules/django_app_agent/core/exceptions.py +359 -0
  140. django_cfg/modules/django_app_agent/models/__init__.py +71 -0
  141. django_cfg/modules/django_app_agent/models/base.py +283 -0
  142. django_cfg/modules/django_app_agent/models/context.py +496 -0
  143. django_cfg/modules/django_app_agent/models/enums.py +481 -0
  144. django_cfg/modules/django_app_agent/models/requests.py +500 -0
  145. django_cfg/modules/django_app_agent/models/responses.py +585 -0
  146. django_cfg/modules/django_app_agent/pytest.ini +6 -0
  147. django_cfg/modules/django_app_agent/services/__init__.py +42 -0
  148. django_cfg/modules/django_app_agent/services/app_generator/__init__.py +30 -0
  149. django_cfg/modules/django_app_agent/services/app_generator/ai_integration.py +133 -0
  150. django_cfg/modules/django_app_agent/services/app_generator/context.py +40 -0
  151. django_cfg/modules/django_app_agent/services/app_generator/main.py +202 -0
  152. django_cfg/modules/django_app_agent/services/app_generator/structure.py +316 -0
  153. django_cfg/modules/django_app_agent/services/app_generator/validation.py +125 -0
  154. django_cfg/modules/django_app_agent/services/base.py +437 -0
  155. django_cfg/modules/django_app_agent/services/context_builder/__init__.py +34 -0
  156. django_cfg/modules/django_app_agent/services/context_builder/code_extractor.py +141 -0
  157. django_cfg/modules/django_app_agent/services/context_builder/context_generator.py +276 -0
  158. django_cfg/modules/django_app_agent/services/context_builder/main.py +272 -0
  159. django_cfg/modules/django_app_agent/services/context_builder/models.py +40 -0
  160. django_cfg/modules/django_app_agent/services/context_builder/pattern_analyzer.py +85 -0
  161. django_cfg/modules/django_app_agent/services/project_scanner/__init__.py +31 -0
  162. django_cfg/modules/django_app_agent/services/project_scanner/app_discovery.py +311 -0
  163. django_cfg/modules/django_app_agent/services/project_scanner/main.py +221 -0
  164. django_cfg/modules/django_app_agent/services/project_scanner/models.py +59 -0
  165. django_cfg/modules/django_app_agent/services/project_scanner/pattern_detection.py +94 -0
  166. django_cfg/modules/django_app_agent/services/questioning_service/__init__.py +28 -0
  167. django_cfg/modules/django_app_agent/services/questioning_service/main.py +273 -0
  168. django_cfg/modules/django_app_agent/services/questioning_service/models.py +111 -0
  169. django_cfg/modules/django_app_agent/services/questioning_service/question_generator.py +251 -0
  170. django_cfg/modules/django_app_agent/services/questioning_service/response_processor.py +347 -0
  171. django_cfg/modules/django_app_agent/services/questioning_service/session_manager.py +356 -0
  172. django_cfg/modules/django_app_agent/services/report_service.py +332 -0
  173. django_cfg/modules/django_app_agent/services/template_manager/__init__.py +18 -0
  174. django_cfg/modules/django_app_agent/services/template_manager/jinja_engine.py +236 -0
  175. django_cfg/modules/django_app_agent/services/template_manager/main.py +159 -0
  176. django_cfg/modules/django_app_agent/services/template_manager/models.py +36 -0
  177. django_cfg/modules/django_app_agent/services/template_manager/template_loader.py +100 -0
  178. django_cfg/modules/django_app_agent/services/template_manager/templates/admin.py.j2 +105 -0
  179. django_cfg/modules/django_app_agent/services/template_manager/templates/apps.py.j2 +31 -0
  180. django_cfg/modules/django_app_agent/services/template_manager/templates/cfg_config.py.j2 +44 -0
  181. django_cfg/modules/django_app_agent/services/template_manager/templates/cfg_module.py.j2 +81 -0
  182. django_cfg/modules/django_app_agent/services/template_manager/templates/forms.py.j2 +107 -0
  183. django_cfg/modules/django_app_agent/services/template_manager/templates/models.py.j2 +139 -0
  184. django_cfg/modules/django_app_agent/services/template_manager/templates/serializers.py.j2 +91 -0
  185. django_cfg/modules/django_app_agent/services/template_manager/templates/tests.py.j2 +195 -0
  186. django_cfg/modules/django_app_agent/services/template_manager/templates/urls.py.j2 +35 -0
  187. django_cfg/modules/django_app_agent/services/template_manager/templates/views.py.j2 +211 -0
  188. django_cfg/modules/django_app_agent/services/template_manager/variable_processor.py +200 -0
  189. django_cfg/modules/django_app_agent/services/validation_service/__init__.py +25 -0
  190. django_cfg/modules/django_app_agent/services/validation_service/django_validator.py +333 -0
  191. django_cfg/modules/django_app_agent/services/validation_service/main.py +242 -0
  192. django_cfg/modules/django_app_agent/services/validation_service/models.py +66 -0
  193. django_cfg/modules/django_app_agent/services/validation_service/quality_validator.py +352 -0
  194. django_cfg/modules/django_app_agent/services/validation_service/security_validator.py +272 -0
  195. django_cfg/modules/django_app_agent/services/validation_service/syntax_validator.py +203 -0
  196. django_cfg/modules/django_app_agent/ui/__init__.py +25 -0
  197. django_cfg/modules/django_app_agent/ui/cli.py +419 -0
  198. django_cfg/modules/django_app_agent/ui/rich_components.py +622 -0
  199. django_cfg/modules/django_app_agent/utils/__init__.py +38 -0
  200. django_cfg/modules/django_app_agent/utils/logging.py +360 -0
  201. django_cfg/modules/django_app_agent/utils/validation.py +417 -0
  202. django_cfg/modules/django_currency/__init__.py +2 -2
  203. django_cfg/modules/django_currency/clients/__init__.py +2 -2
  204. django_cfg/modules/django_currency/clients/hybrid_client.py +587 -0
  205. django_cfg/modules/django_currency/core/converter.py +12 -12
  206. django_cfg/modules/django_currency/database/__init__.py +2 -2
  207. django_cfg/modules/django_currency/database/database_loader.py +93 -42
  208. django_cfg/modules/django_llm/llm/client.py +10 -2
  209. django_cfg/modules/django_unfold/callbacks/actions.py +1 -1
  210. django_cfg/modules/django_unfold/callbacks/statistics.py +1 -1
  211. django_cfg/modules/django_unfold/dashboard.py +14 -13
  212. django_cfg/modules/django_unfold/models/config.py +1 -1
  213. django_cfg/registry/core.py +3 -0
  214. django_cfg/registry/third_party.py +2 -2
  215. django_cfg/template_archive/django_sample.zip +0 -0
  216. {django_cfg-1.3.5.dist-info → django_cfg-1.3.9.dist-info}/METADATA +2 -1
  217. {django_cfg-1.3.5.dist-info → django_cfg-1.3.9.dist-info}/RECORD +224 -118
  218. django_cfg/apps/accounts/admin/activity.py +0 -96
  219. django_cfg/apps/accounts/admin/group.py +0 -17
  220. django_cfg/apps/accounts/admin/otp.py +0 -59
  221. django_cfg/apps/accounts/admin/registration_source.py +0 -97
  222. django_cfg/apps/accounts/admin/twilio_response.py +0 -227
  223. django_cfg/apps/accounts/admin/user.py +0 -300
  224. django_cfg/apps/agents/core/agent.py +0 -281
  225. django_cfg/apps/payments/admin_interface/old/payments/base.html +0 -175
  226. django_cfg/apps/payments/admin_interface/old/payments/components/dev_tool_card.html +0 -125
  227. django_cfg/apps/payments/admin_interface/old/payments/components/loading_spinner.html +0 -16
  228. django_cfg/apps/payments/admin_interface/old/payments/components/ngrok_status_card.html +0 -113
  229. django_cfg/apps/payments/admin_interface/old/payments/components/notification.html +0 -27
  230. django_cfg/apps/payments/admin_interface/old/payments/components/provider_card.html +0 -86
  231. django_cfg/apps/payments/admin_interface/old/payments/components/status_card.html +0 -35
  232. django_cfg/apps/payments/admin_interface/old/payments/currency_converter.html +0 -382
  233. django_cfg/apps/payments/admin_interface/old/payments/payment_dashboard.html +0 -309
  234. django_cfg/apps/payments/admin_interface/old/payments/payment_form.html +0 -303
  235. django_cfg/apps/payments/admin_interface/old/payments/payment_list.html +0 -382
  236. django_cfg/apps/payments/admin_interface/old/payments/payment_status.html +0 -500
  237. django_cfg/apps/payments/admin_interface/old/payments/webhook_dashboard.html +0 -518
  238. django_cfg/apps/payments/admin_interface/old/static/payments/css/components.css +0 -619
  239. django_cfg/apps/payments/admin_interface/old/static/payments/css/dashboard.css +0 -188
  240. django_cfg/apps/payments/admin_interface/old/static/payments/js/components.js +0 -545
  241. django_cfg/apps/payments/admin_interface/old/static/payments/js/ngrok-status.js +0 -163
  242. django_cfg/apps/payments/admin_interface/old/static/payments/js/utils.js +0 -412
  243. django_cfg/apps/tasks/admin.py +0 -320
  244. django_cfg/middleware/static_nocache.py +0 -55
  245. django_cfg/modules/django_currency/clients/yahoo_client.py +0 -157
  246. /django_cfg/modules/{django_unfold → django_admin}/icons/README.md +0 -0
  247. /django_cfg/modules/{django_unfold → django_admin}/icons/__init__.py +0 -0
  248. /django_cfg/modules/{django_unfold → django_admin}/icons/constants.py +0 -0
  249. /django_cfg/modules/{django_unfold → django_admin}/icons/generate_icons.py +0 -0
  250. {django_cfg-1.3.5.dist-info → django_cfg-1.3.9.dist-info}/WHEEL +0 -0
  251. {django_cfg-1.3.5.dist-info → django_cfg-1.3.9.dist-info}/entry_points.txt +0 -0
  252. {django_cfg-1.3.5.dist-info → django_cfg-1.3.9.dist-info}/licenses/LICENSE +0 -0
@@ -1,548 +1,258 @@
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
+ 'created_display'
66
60
  ]
67
61
 
68
62
  list_filter = [
69
- PaymentStatusFilter,
70
- PaymentAmountFilter,
71
- UserEmailFilter,
72
- RecentActivityFilter,
63
+ 'status',
73
64
  'provider',
74
65
  'currency',
75
66
  'created_at'
76
67
  ]
77
68
 
69
+ search_fields = [
70
+ 'internal_payment_id',
71
+ 'transaction_hash',
72
+ 'user__username',
73
+ 'user__email',
74
+ 'pay_address'
75
+ ]
76
+
78
77
  readonly_fields = [
79
- 'id',
80
- 'provider_payment_id',
81
- 'payment_url',
78
+ 'internal_payment_id',
82
79
  'created_at',
83
80
  '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
- })
81
+ 'payment_details_display'
142
82
  ]
143
83
 
144
- def get_queryset(self, request):
145
- """Optimize queryset with user data."""
146
- return super().get_queryset(request).select_related('user')
84
+ # Register actions
85
+ actions = ['mark_as_completed', 'mark_as_failed', 'cancel_payments']
147
86
 
148
- @display(description="Payment ID", ordering='id')
87
+ # Display methods using utilities
88
+ @display(description="Payment ID")
149
89
  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
90
+ """Payment ID display with badge."""
91
+ return StatusBadge.create(
92
+ text=obj.internal_payment_id[:12] + "...",
93
+ variant="info"
157
94
  )
158
95
 
159
- @display(description="User", ordering='user__email')
96
+ @display(description="User", header=True)
160
97
  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>')
98
+ """User display with avatar."""
99
+ return self.display_user_with_avatar(obj, 'user')
179
100
 
180
- @display(description="Amount", ordering='amount_usd')
101
+ @display(description="Amount")
181
102
  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
- )
103
+ """Amount display with currency."""
104
+ # Get currency code from currency relation or default to USD
105
+ currency_code = "USD"
106
+ if obj.currency:
107
+ currency_code = getattr(obj.currency, 'code', 'USD')
196
108
 
197
- return format_html(
198
- '<div class="text-right font-bold text-green-600 dark:text-green-400">{}</div>',
199
- usd_amount
109
+ config = MoneyDisplayConfig(
110
+ currency=currency_code,
111
+ show_sign=False,
112
+ thousand_separator=True
200
113
  )
114
+
115
+ return self.display_money_amount(obj, 'amount_usd', config)
201
116
 
202
- @display(description="Status", ordering='status')
117
+ @display(description="Status", label=True)
203
118
  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'),
119
+ """Status display with payment-specific colors."""
120
+ # Payment-specific status mappings
121
+ payment_status_mappings = {
122
+ 'pending': 'warning',
123
+ 'processing': 'info',
124
+ 'completed': 'success',
125
+ 'failed': 'danger',
126
+ 'cancelled': 'secondary',
127
+ 'expired': 'danger',
128
+ 'refunded': 'warning'
214
129
  }
215
130
 
216
- icon, color_class, label = status_config.get(
217
- obj.status,
218
- ('❓', 'bg-gray-100 text-gray-800', 'Unknown')
131
+ config = StatusBadgeConfig(
132
+ custom_mappings=payment_status_mappings,
133
+ show_icons=True
219
134
  )
220
135
 
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
- )
136
+ return self.display_status_auto(obj, 'status', config)
229
137
 
230
- @display(description="Provider", ordering='provider')
138
+ @display(description="Provider")
231
139
  def provider_display(self, obj):
232
- """Display provider with logo/icon."""
140
+ """Provider display with badge and icons."""
141
+ # Provider-specific styling and icons
233
142
  provider_config = {
234
- 'nowpayments': ('đŸŸĻ', 'NowPayments'),
235
- 'cryptomus': ('🟩', 'Cryptomus'),
236
- 'cryptapi': ('đŸŸĒ', 'CryptAPI'),
143
+ 'stripe': {'variant': 'primary', 'icon': Icons.CREDIT_CARD},
144
+ 'paypal': {'variant': 'info', 'icon': Icons.ACCOUNT_BALANCE_WALLET},
145
+ 'crypto': {'variant': 'warning', 'icon': Icons.CURRENCY_BITCOIN},
146
+ 'bank': {'variant': 'success', 'icon': Icons.ACCOUNT_BALANCE},
237
147
  }
238
148
 
239
- icon, name = provider_config.get(obj.provider, ('🔷', obj.provider.title()))
149
+ config = provider_config.get(obj.provider.lower(), {'variant': 'secondary', 'icon': Icons.PAYMENT})
240
150
 
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
151
+ badge_config = StatusBadgeConfig(
152
+ show_icons=True,
153
+ icon=config['icon']
248
154
  )
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
-
268
- return format_html('<span class="text-gray-500">—</span>')
269
-
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
155
 
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)
156
+ return StatusBadge.create(
157
+ text=obj.provider.title(),
158
+ variant=config['variant'],
159
+ config=badge_config
308
160
  )
309
161
 
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)
162
+ @display(description="Created")
163
+ def created_display(self, obj):
164
+ """Created time display."""
165
+ return self.display_datetime_relative(
166
+ obj,
167
+ 'created_at',
168
+ DateTimeDisplayConfig(show_relative=True, show_seconds=False)
320
169
  )
321
170
 
322
- def changelist_view(self, request, extra_context=None):
323
- """Add payment statistics to changelist context."""
324
- extra_context = extra_context or {}
171
+ # Readonly field displays
172
+ def payment_details_display(self, obj):
173
+ """Detailed payment information for detail view."""
174
+ if not obj.pk:
175
+ return "Save to see details"
325
176
 
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
177
+ from django.utils.html import format_html
178
+ from django_cfg.modules.django_admin.utils.displays import MoneyDisplay
381
179
 
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."""
180
+ # Calculate age
181
+ age = timezone.now() - obj.created_at
182
+ age_text = f"{age.days} days, {age.seconds // 3600} hours"
393
183
 
394
- updated_count = 0
395
- error_count = 0
184
+ # Build details HTML
185
+ details = []
396
186
 
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}")
187
+ # Basic info
188
+ details.append(f"<strong>Internal ID:</strong> {obj.internal_payment_id}")
189
+ details.append(f"<strong>Age:</strong> {age_text}")
413
190
 
414
- if updated_count > 0:
415
- messages.success(
416
- request,
417
- f"✅ Checked status for {updated_count} payments"
418
- )
191
+ # Transaction details
192
+ if obj.transaction_hash:
193
+ details.append(f"<strong>Transaction Hash:</strong> {obj.transaction_hash}")
419
194
 
420
- if error_count > 0:
421
- messages.warning(
422
- request,
423
- f"âš ī¸ Failed to check {error_count} payments"
195
+ if obj.pay_address:
196
+ details.append(f"<strong>Pay Address:</strong> {obj.pay_address}")
197
+
198
+ if obj.pay_amount:
199
+ pay_amount_html = MoneyDisplay.amount(
200
+ obj.pay_amount,
201
+ MoneyDisplayConfig(currency="USD")
424
202
  )
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."""
203
+ details.append(f"<strong>Pay Amount:</strong> {pay_amount_html}")
433
204
 
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
- )
205
+ # URLs
206
+ if obj.callback_url:
207
+ details.append(f"<strong>Callback URL:</strong> {obj.callback_url}")
441
208
 
442
- cancelled_count = 0
209
+ if obj.cancel_url:
210
+ details.append(f"<strong>Cancel URL:</strong> {obj.cancel_url}")
443
211
 
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}")
212
+ # Description
213
+ if obj.description:
214
+ details.append(f"<strong>Description:</strong> {obj.description}")
451
215
 
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
- )
216
+ return format_html("<br>".join(details))
464
217
 
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
- ]
218
+ payment_details_display.short_description = "Payment Details"
219
+
220
+ # Actions
221
+ @action(description="Mark as completed", variant=ActionVariant.SUCCESS)
222
+ def mark_completed(self, request, queryset):
223
+ """Mark selected payments as completed."""
224
+ updated = queryset.filter(
225
+ status__in=['pending', 'processing']
226
+ ).update(status='completed')
227
+
228
+ self.message_user(
229
+ request,
230
+ f"Successfully marked {updated} payment(s) as completed.",
231
+ level='SUCCESS'
480
232
  )
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(
233
+
234
+ @action(description="Mark as failed", variant=ActionVariant.DANGER)
235
+ def mark_failed(self, request, queryset):
236
+ """Mark selected payments as failed."""
237
+ updated = queryset.filter(
238
+ status__in=['pending', 'processing']
239
+ ).update(status='failed')
240
+
241
+ self.message_user(
505
242
  request,
506
- f"â„šī¸ Skipped {skipped_count} payments (not completable)"
507
- )
243
+ f"Successfully marked {updated} payment(s) as failed.",
244
+ level='WARNING'
245
+ )
508
246
 
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
- ])
247
+ @action(description="Cancel payments", variant=ActionVariant.WARNING)
248
+ def cancel_payments(self, request, queryset):
249
+ """Cancel selected payments."""
250
+ updated = queryset.filter(
251
+ status__in=['pending', 'processing']
252
+ ).update(status='cancelled')
542
253
 
543
- messages.success(
254
+ self.message_user(
544
255
  request,
545
- f"📊 Exported {queryset.count()} payments to CSV"
256
+ f"Successfully cancelled {updated} payment(s).",
257
+ level='WARNING'
546
258
  )
547
-
548
- return response