django-cfg 1.2.31__py3-none-any.whl → 1.3.3__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 (264) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/api/health/views.py +4 -2
  3. django_cfg/apps/knowbase/config/settings.py +16 -15
  4. django_cfg/apps/payments/README.md +326 -0
  5. django_cfg/apps/payments/admin/__init__.py +20 -10
  6. django_cfg/apps/payments/admin/api_keys_admin.py +521 -237
  7. django_cfg/apps/payments/admin/balance_admin.py +592 -297
  8. django_cfg/apps/payments/admin/currencies_admin.py +526 -222
  9. django_cfg/apps/payments/admin/filters.py +306 -199
  10. django_cfg/apps/payments/admin/payments_admin.py +465 -70
  11. django_cfg/apps/payments/admin/subscriptions_admin.py +578 -128
  12. django_cfg/apps/payments/admin_interface/__init__.py +18 -0
  13. django_cfg/apps/payments/admin_interface/templates/payments/base.html +162 -0
  14. django_cfg/apps/payments/admin_interface/templates/payments/components/dev_tool_card.html +38 -0
  15. django_cfg/apps/payments/admin_interface/templates/payments/components/loading_spinner.html +16 -0
  16. django_cfg/apps/payments/admin_interface/templates/payments/components/notification.html +27 -0
  17. django_cfg/apps/payments/admin_interface/templates/payments/components/provider_card.html +86 -0
  18. django_cfg/apps/payments/admin_interface/templates/payments/components/status_card.html +39 -0
  19. django_cfg/apps/payments/admin_interface/templates/payments/currency_converter.html +382 -0
  20. django_cfg/apps/payments/admin_interface/templates/payments/payment_dashboard.html +300 -0
  21. django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +303 -0
  22. django_cfg/apps/payments/admin_interface/templates/payments/payment_list.html +382 -0
  23. django_cfg/apps/payments/admin_interface/templates/payments/payment_status.html +500 -0
  24. django_cfg/apps/payments/admin_interface/templates/payments/webhook_dashboard.html +594 -0
  25. django_cfg/apps/payments/admin_interface/views/__init__.py +23 -0
  26. django_cfg/apps/payments/admin_interface/views/payment_views.py +259 -0
  27. django_cfg/apps/payments/admin_interface/views/webhook_dashboard.py +37 -0
  28. django_cfg/apps/payments/apps.py +34 -9
  29. django_cfg/apps/payments/config/__init__.py +28 -51
  30. django_cfg/apps/payments/config/constance/__init__.py +22 -0
  31. django_cfg/apps/payments/config/constance/config_service.py +123 -0
  32. django_cfg/apps/payments/config/constance/fields.py +69 -0
  33. django_cfg/apps/payments/config/constance/settings.py +160 -0
  34. django_cfg/apps/payments/config/django_cfg_integration.py +202 -0
  35. django_cfg/apps/payments/config/helpers.py +130 -0
  36. django_cfg/apps/payments/management/__init__.py +1 -3
  37. django_cfg/apps/payments/management/commands/__init__.py +1 -3
  38. django_cfg/apps/payments/management/commands/cleanup_expired_data.py +419 -0
  39. django_cfg/apps/payments/management/commands/currency_stats.py +297 -225
  40. django_cfg/apps/payments/management/commands/manage_currencies.py +303 -151
  41. django_cfg/apps/payments/management/commands/manage_providers.py +333 -160
  42. django_cfg/apps/payments/management/commands/process_pending_payments.py +357 -0
  43. django_cfg/apps/payments/management/commands/test_providers.py +434 -0
  44. django_cfg/apps/payments/middleware/__init__.py +3 -1
  45. django_cfg/apps/payments/middleware/api_access.py +329 -222
  46. django_cfg/apps/payments/middleware/rate_limiting.py +342 -152
  47. django_cfg/apps/payments/middleware/usage_tracking.py +249 -240
  48. django_cfg/apps/payments/migrations/0001_initial.py +708 -536
  49. django_cfg/apps/payments/models/__init__.py +13 -18
  50. django_cfg/apps/payments/models/api_keys.py +121 -43
  51. django_cfg/apps/payments/models/balance.py +153 -115
  52. django_cfg/apps/payments/models/base.py +68 -15
  53. django_cfg/apps/payments/models/currencies.py +172 -148
  54. django_cfg/apps/payments/models/managers/__init__.py +44 -0
  55. django_cfg/apps/payments/models/managers/api_key_managers.py +329 -0
  56. django_cfg/apps/payments/models/managers/balance_managers.py +599 -0
  57. django_cfg/apps/payments/models/managers/currency_managers.py +385 -0
  58. django_cfg/apps/payments/models/managers/payment_managers.py +511 -0
  59. django_cfg/apps/payments/models/managers/subscription_managers.py +641 -0
  60. django_cfg/apps/payments/models/payments.py +235 -285
  61. django_cfg/apps/payments/models/subscriptions.py +257 -177
  62. django_cfg/apps/payments/models/tariffs.py +147 -40
  63. django_cfg/apps/payments/services/__init__.py +209 -56
  64. django_cfg/apps/payments/services/cache/__init__.py +6 -6
  65. django_cfg/apps/payments/services/cache_service/__init__.py +143 -0
  66. django_cfg/apps/payments/services/cache_service/api_key_cache.py +37 -0
  67. django_cfg/apps/payments/services/{cache/base.py → cache_service/interfaces.py} +3 -1
  68. django_cfg/apps/payments/services/cache_service/keys.py +49 -0
  69. django_cfg/apps/payments/services/cache_service/rate_limit_cache.py +47 -0
  70. django_cfg/apps/payments/services/cache_service/simple_cache.py +101 -0
  71. django_cfg/apps/payments/services/core/__init__.py +10 -6
  72. django_cfg/apps/payments/services/core/balance_service.py +435 -360
  73. django_cfg/apps/payments/services/core/base.py +166 -0
  74. django_cfg/apps/payments/services/core/currency_service.py +478 -0
  75. django_cfg/apps/payments/services/core/payment_service.py +371 -465
  76. django_cfg/apps/payments/services/core/subscription_service.py +425 -481
  77. django_cfg/apps/payments/services/core/webhook_service.py +410 -0
  78. django_cfg/apps/payments/services/integrations/__init__.py +29 -0
  79. django_cfg/apps/payments/services/integrations/ngrok_service.py +47 -0
  80. django_cfg/apps/payments/services/integrations/providers_config.py +107 -0
  81. django_cfg/apps/payments/services/providers/__init__.py +9 -14
  82. django_cfg/apps/payments/services/providers/base.py +234 -174
  83. django_cfg/apps/payments/services/providers/nowpayments.py +478 -0
  84. django_cfg/apps/payments/services/providers/registry.py +367 -301
  85. django_cfg/apps/payments/services/types/__init__.py +78 -0
  86. django_cfg/apps/payments/services/types/data.py +177 -0
  87. django_cfg/apps/payments/services/types/requests.py +150 -0
  88. django_cfg/apps/payments/services/types/responses.py +156 -0
  89. django_cfg/apps/payments/services/types/webhooks.py +232 -0
  90. django_cfg/apps/payments/signals/__init__.py +33 -8
  91. django_cfg/apps/payments/signals/api_key_signals.py +210 -129
  92. django_cfg/apps/payments/signals/balance_signals.py +174 -0
  93. django_cfg/apps/payments/signals/payment_signals.py +128 -103
  94. django_cfg/apps/payments/signals/subscription_signals.py +194 -142
  95. django_cfg/apps/payments/static/payments/css/components.css +380 -0
  96. django_cfg/apps/payments/static/payments/css/dashboard.css +188 -0
  97. django_cfg/apps/payments/static/payments/js/components.js +545 -0
  98. django_cfg/apps/payments/static/payments/js/utils.js +412 -0
  99. django_cfg/apps/payments/templatetags/__init__.py +1 -1
  100. django_cfg/apps/payments/templatetags/payment_tags.py +466 -0
  101. django_cfg/apps/payments/urls.py +45 -48
  102. django_cfg/apps/payments/urls_admin.py +33 -42
  103. django_cfg/apps/payments/views/api/__init__.py +101 -0
  104. django_cfg/apps/payments/views/api/api_keys.py +387 -0
  105. django_cfg/apps/payments/views/api/balances.py +381 -0
  106. django_cfg/apps/payments/views/api/base.py +298 -0
  107. django_cfg/apps/payments/views/api/currencies.py +402 -0
  108. django_cfg/apps/payments/views/api/payments.py +415 -0
  109. django_cfg/apps/payments/views/api/subscriptions.py +475 -0
  110. django_cfg/apps/payments/views/api/webhooks.py +476 -0
  111. django_cfg/apps/payments/views/serializers/__init__.py +99 -0
  112. django_cfg/apps/payments/views/serializers/api_keys.py +424 -0
  113. django_cfg/apps/payments/views/serializers/balances.py +300 -0
  114. django_cfg/apps/payments/views/serializers/currencies.py +335 -0
  115. django_cfg/apps/payments/views/serializers/payments.py +387 -0
  116. django_cfg/apps/payments/views/serializers/subscriptions.py +429 -0
  117. django_cfg/apps/payments/views/serializers/webhooks.py +137 -0
  118. django_cfg/config.py +1 -1
  119. django_cfg/core/config.py +40 -4
  120. django_cfg/core/generation.py +25 -4
  121. django_cfg/core/integration/README.md +363 -0
  122. django_cfg/core/integration/__init__.py +47 -0
  123. django_cfg/core/integration/commands_collector.py +239 -0
  124. django_cfg/core/integration/display/__init__.py +15 -0
  125. django_cfg/core/integration/display/base.py +157 -0
  126. django_cfg/core/integration/display/ngrok.py +164 -0
  127. django_cfg/core/integration/display/startup.py +815 -0
  128. django_cfg/core/integration/url_integration.py +123 -0
  129. django_cfg/core/integration/version_checker.py +160 -0
  130. django_cfg/management/commands/auto_generate.py +4 -0
  131. django_cfg/management/commands/check_settings.py +6 -0
  132. django_cfg/management/commands/clear_constance.py +5 -2
  133. django_cfg/management/commands/create_token.py +6 -0
  134. django_cfg/management/commands/list_urls.py +6 -0
  135. django_cfg/management/commands/migrate_all.py +6 -0
  136. django_cfg/management/commands/migrator.py +3 -0
  137. django_cfg/management/commands/rundramatiq.py +6 -0
  138. django_cfg/management/commands/runserver_ngrok.py +51 -29
  139. django_cfg/management/commands/script.py +6 -0
  140. django_cfg/management/commands/show_config.py +12 -2
  141. django_cfg/management/commands/show_urls.py +4 -0
  142. django_cfg/management/commands/superuser.py +6 -0
  143. django_cfg/management/commands/task_clear.py +4 -1
  144. django_cfg/management/commands/task_status.py +3 -1
  145. django_cfg/management/commands/test_email.py +3 -0
  146. django_cfg/management/commands/test_telegram.py +6 -0
  147. django_cfg/management/commands/test_twilio.py +6 -0
  148. django_cfg/management/commands/tree.py +6 -0
  149. django_cfg/management/commands/validate_config.py +155 -149
  150. django_cfg/models/constance.py +31 -11
  151. django_cfg/models/payments.py +175 -492
  152. django_cfg/modules/django_logger.py +160 -146
  153. django_cfg/modules/django_unfold/dashboard.py +64 -16
  154. django_cfg/registry/core.py +1 -0
  155. django_cfg/template_archive/django_sample.zip +0 -0
  156. django_cfg/utils/smart_defaults.py +227 -570
  157. django_cfg/utils/toolkit.py +51 -11
  158. {django_cfg-1.2.31.dist-info → django_cfg-1.3.3.dist-info}/METADATA +4 -1
  159. {django_cfg-1.2.31.dist-info → django_cfg-1.3.3.dist-info}/RECORD +162 -185
  160. django_cfg/apps/payments/__init__.py +0 -8
  161. django_cfg/apps/payments/admin/tariffs_admin.py +0 -199
  162. django_cfg/apps/payments/config/module.py +0 -70
  163. django_cfg/apps/payments/config/providers.py +0 -105
  164. django_cfg/apps/payments/config/settings.py +0 -96
  165. django_cfg/apps/payments/config/utils.py +0 -52
  166. django_cfg/apps/payments/decorators.py +0 -291
  167. django_cfg/apps/payments/management/commands/README.md +0 -146
  168. django_cfg/apps/payments/managers/__init__.py +0 -23
  169. django_cfg/apps/payments/managers/api_key_manager.py +0 -35
  170. django_cfg/apps/payments/managers/balance_manager.py +0 -361
  171. django_cfg/apps/payments/managers/currency_manager.py +0 -306
  172. django_cfg/apps/payments/managers/payment_manager.py +0 -192
  173. django_cfg/apps/payments/managers/subscription_manager.py +0 -37
  174. django_cfg/apps/payments/managers/tariff_manager.py +0 -29
  175. django_cfg/apps/payments/migrations/0002_network_providercurrency_and_more.py +0 -241
  176. django_cfg/apps/payments/migrations/0003_add_usd_rate_cache.py +0 -30
  177. django_cfg/apps/payments/models/events.py +0 -73
  178. django_cfg/apps/payments/serializers/__init__.py +0 -57
  179. django_cfg/apps/payments/serializers/api_keys.py +0 -51
  180. django_cfg/apps/payments/serializers/balance.py +0 -59
  181. django_cfg/apps/payments/serializers/currencies.py +0 -63
  182. django_cfg/apps/payments/serializers/payments.py +0 -62
  183. django_cfg/apps/payments/serializers/subscriptions.py +0 -71
  184. django_cfg/apps/payments/serializers/tariffs.py +0 -56
  185. django_cfg/apps/payments/services/billing/__init__.py +0 -8
  186. django_cfg/apps/payments/services/cache/simple_cache.py +0 -135
  187. django_cfg/apps/payments/services/core/fallback_service.py +0 -432
  188. django_cfg/apps/payments/services/internal_types.py +0 -461
  189. django_cfg/apps/payments/services/middleware/__init__.py +0 -8
  190. django_cfg/apps/payments/services/monitoring/__init__.py +0 -22
  191. django_cfg/apps/payments/services/monitoring/api_schemas.py +0 -76
  192. django_cfg/apps/payments/services/monitoring/provider_health.py +0 -372
  193. django_cfg/apps/payments/services/providers/cryptapi/__init__.py +0 -4
  194. django_cfg/apps/payments/services/providers/cryptapi/config.py +0 -8
  195. django_cfg/apps/payments/services/providers/cryptapi/models.py +0 -192
  196. django_cfg/apps/payments/services/providers/cryptapi/provider.py +0 -439
  197. django_cfg/apps/payments/services/providers/cryptomus/__init__.py +0 -4
  198. django_cfg/apps/payments/services/providers/cryptomus/models.py +0 -176
  199. django_cfg/apps/payments/services/providers/cryptomus/provider.py +0 -429
  200. django_cfg/apps/payments/services/providers/cryptomus/provider_v2.py +0 -564
  201. django_cfg/apps/payments/services/providers/models/__init__.py +0 -34
  202. django_cfg/apps/payments/services/providers/models/currencies.py +0 -190
  203. django_cfg/apps/payments/services/providers/nowpayments/__init__.py +0 -4
  204. django_cfg/apps/payments/services/providers/nowpayments/models.py +0 -196
  205. django_cfg/apps/payments/services/providers/nowpayments/provider.py +0 -380
  206. django_cfg/apps/payments/services/providers/stripe/__init__.py +0 -4
  207. django_cfg/apps/payments/services/providers/stripe/models.py +0 -184
  208. django_cfg/apps/payments/services/providers/stripe/provider.py +0 -109
  209. django_cfg/apps/payments/services/security/__init__.py +0 -34
  210. django_cfg/apps/payments/services/security/error_handler.py +0 -635
  211. django_cfg/apps/payments/services/security/payment_notifications.py +0 -342
  212. django_cfg/apps/payments/services/security/webhook_validator.py +0 -474
  213. django_cfg/apps/payments/static/payments/css/payments.css +0 -340
  214. django_cfg/apps/payments/static/payments/js/notifications.js +0 -202
  215. django_cfg/apps/payments/static/payments/js/payment-utils.js +0 -318
  216. django_cfg/apps/payments/static/payments/js/theme.js +0 -86
  217. django_cfg/apps/payments/tasks/__init__.py +0 -12
  218. django_cfg/apps/payments/tasks/webhook_processing.py +0 -177
  219. django_cfg/apps/payments/templates/admin/payments/currency/change_list.html +0 -50
  220. django_cfg/apps/payments/templates/payments/base.html +0 -182
  221. django_cfg/apps/payments/templates/payments/components/payment_card.html +0 -201
  222. django_cfg/apps/payments/templates/payments/components/payment_qr_code.html +0 -109
  223. django_cfg/apps/payments/templates/payments/components/progress_bar.html +0 -43
  224. django_cfg/apps/payments/templates/payments/components/provider_stats.html +0 -40
  225. django_cfg/apps/payments/templates/payments/components/status_badge.html +0 -34
  226. django_cfg/apps/payments/templates/payments/components/status_overview.html +0 -148
  227. django_cfg/apps/payments/templates/payments/dashboard.html +0 -258
  228. django_cfg/apps/payments/templates/payments/dashboard_simple_test.html +0 -35
  229. django_cfg/apps/payments/templates/payments/payment_create.html +0 -579
  230. django_cfg/apps/payments/templates/payments/payment_detail.html +0 -373
  231. django_cfg/apps/payments/templates/payments/payment_list.html +0 -354
  232. django_cfg/apps/payments/templates/payments/stats.html +0 -261
  233. django_cfg/apps/payments/templates/payments/test.html +0 -213
  234. django_cfg/apps/payments/templatetags/payments_tags.py +0 -315
  235. django_cfg/apps/payments/utils/__init__.py +0 -43
  236. django_cfg/apps/payments/utils/billing_utils.py +0 -342
  237. django_cfg/apps/payments/utils/config_utils.py +0 -239
  238. django_cfg/apps/payments/utils/middleware_utils.py +0 -228
  239. django_cfg/apps/payments/utils/validation_utils.py +0 -94
  240. django_cfg/apps/payments/views/__init__.py +0 -63
  241. django_cfg/apps/payments/views/api_key_views.py +0 -164
  242. django_cfg/apps/payments/views/balance_views.py +0 -75
  243. django_cfg/apps/payments/views/currency_views.py +0 -122
  244. django_cfg/apps/payments/views/payment_views.py +0 -149
  245. django_cfg/apps/payments/views/subscription_views.py +0 -135
  246. django_cfg/apps/payments/views/tariff_views.py +0 -131
  247. django_cfg/apps/payments/views/templates/__init__.py +0 -25
  248. django_cfg/apps/payments/views/templates/ajax.py +0 -451
  249. django_cfg/apps/payments/views/templates/base.py +0 -212
  250. django_cfg/apps/payments/views/templates/dashboard.py +0 -60
  251. django_cfg/apps/payments/views/templates/payment_detail.py +0 -102
  252. django_cfg/apps/payments/views/templates/payment_management.py +0 -158
  253. django_cfg/apps/payments/views/templates/qr_code.py +0 -174
  254. django_cfg/apps/payments/views/templates/stats.py +0 -244
  255. django_cfg/apps/payments/views/templates/utils.py +0 -181
  256. django_cfg/apps/payments/views/webhook_views.py +0 -266
  257. django_cfg/apps/payments/viewsets.py +0 -66
  258. django_cfg/core/integration.py +0 -160
  259. django_cfg/template_archive/.gitignore +0 -1
  260. django_cfg/template_archive/__init__.py +0 -0
  261. django_cfg/urls.py +0 -33
  262. {django_cfg-1.2.31.dist-info → django_cfg-1.3.3.dist-info}/WHEEL +0 -0
  263. {django_cfg-1.2.31.dist-info → django_cfg-1.3.3.dist-info}/entry_points.txt +0 -0
  264. {django_cfg-1.2.31.dist-info → django_cfg-1.3.3.dist-info}/licenses/LICENSE +0 -0
@@ -1,43 +1,52 @@
1
1
  """
2
- Management command to show currency database statistics.
2
+ Currency Statistics Management Command for Universal Payment System v2.0.
3
3
 
4
- Usage:
5
- python manage.py currency_stats
6
- python manage.py currency_stats --detailed
7
- python manage.py currency_stats --top 10
8
- python manage.py currency_stats --check-rates
4
+ Display comprehensive currency database statistics and health information.
9
5
  """
10
6
 
11
7
  from datetime import datetime, timedelta
12
- from typing import List
8
+ from typing import List, Dict, Any
9
+ from decimal import Decimal
13
10
 
14
- from django.core.management.base import BaseCommand
11
+ from django.core.management.base import BaseCommand, CommandError
15
12
  from django.utils import timezone
16
- from django.db.models import Q, Count, Avg
13
+ from django.db.models import Q, Count, Avg, Sum, Max, Min
14
+ from django.contrib.humanize.templatetags.humanize import intcomma
17
15
 
18
- from django_cfg.apps.payments.models.currencies import Currency
16
+ from django_cfg.modules.django_logger import get_logger
17
+ from django_cfg.apps.payments.models import Currency, Network, ProviderCurrency, UniversalPayment
18
+ from django_cfg.apps.payments.services.providers.registry import get_provider_registry
19
+
20
+ logger = get_logger("currency_stats")
19
21
 
20
22
 
21
23
  class Command(BaseCommand):
22
24
  """
23
25
  Display currency database statistics and health information.
26
+
27
+ Features:
28
+ - Basic and detailed statistics
29
+ - Top currencies by usage and value
30
+ - Rate freshness analysis
31
+ - Provider currency coverage
32
+ - Payment volume analysis
24
33
  """
25
34
 
26
- help = 'Show currency database statistics'
35
+ help = 'Show currency database statistics and health information'
27
36
 
28
37
  def add_arguments(self, parser):
29
38
  """Add command line arguments."""
30
39
  parser.add_argument(
31
40
  '--detailed',
32
41
  action='store_true',
33
- help='Show detailed statistics'
42
+ help='Show detailed statistics breakdown'
34
43
  )
35
44
 
36
45
  parser.add_argument(
37
46
  '--top',
38
47
  type=int,
39
- default=5,
40
- help='Number of top currencies to show (default: 5)'
48
+ default=10,
49
+ help='Number of top currencies to show (default: 10)'
41
50
  )
42
51
 
43
52
  parser.add_argument(
@@ -46,259 +55,322 @@ class Command(BaseCommand):
46
55
  help='Check for outdated exchange rates'
47
56
  )
48
57
 
58
+ parser.add_argument(
59
+ '--provider',
60
+ type=str,
61
+ help='Show statistics for specific provider'
62
+ )
63
+
49
64
  parser.add_argument(
50
65
  '--export-csv',
51
66
  type=str,
52
- help='Export currency data to CSV file'
67
+ help='Export statistics to CSV file'
68
+ )
69
+
70
+ parser.add_argument(
71
+ '--format',
72
+ choices=['table', 'json', 'yaml'],
73
+ default='table',
74
+ help='Output format (default: table)'
53
75
  )
54
76
 
55
77
  def handle(self, *args, **options):
56
- """Main command handler."""
57
-
78
+ """Execute the command."""
79
+ try:
80
+ self.options = options
81
+ self.show_header()
82
+
83
+ if options['check_rates']:
84
+ self.check_rate_freshness()
85
+ elif options['provider']:
86
+ self.show_provider_stats(options['provider'])
87
+ else:
88
+ self.show_general_stats()
89
+
90
+ if options['detailed']:
91
+ self.show_detailed_stats()
92
+
93
+ self.show_top_currencies()
94
+
95
+ if options['export_csv']:
96
+ self.export_to_csv(options['export_csv'])
97
+
98
+ except Exception as e:
99
+ logger.error(f"Currency stats command failed: {e}")
100
+ raise CommandError(f"Failed to generate currency statistics: {e}")
101
+
102
+ def show_header(self):
103
+ """Display command header."""
58
104
  self.stdout.write(
59
- self.style.SUCCESS('📊 Currency Database Statistics')
105
+ self.style.SUCCESS("=" * 60)
60
106
  )
61
- self.stdout.write('=' * 50)
62
-
63
- self._show_basic_stats(options)
64
-
65
- if options['detailed']:
66
- self._show_detailed_stats(options)
67
-
68
- if options['check_rates']:
69
- self._check_rate_freshness()
70
-
71
- if options['export_csv']:
72
- self._export_to_csv(options['export_csv'])
107
+ self.stdout.write(
108
+ self.style.SUCCESS("📊 CURRENCY DATABASE STATISTICS")
109
+ )
110
+ self.stdout.write(
111
+ self.style.SUCCESS("=" * 60)
112
+ )
113
+ self.stdout.write(f"Generated: {timezone.now().strftime('%Y-%m-%d %H:%M:%S UTC')}")
114
+ self.stdout.write("")
73
115
 
74
- def _show_basic_stats(self, options):
75
- """Show basic currency statistics."""
76
-
116
+ def show_general_stats(self):
117
+ """Show general currency statistics."""
77
118
  # Basic counts
78
- total = Currency.objects.count()
79
- active = Currency.objects.count()
80
- inactive = total - active
81
-
82
- fiat_count = Currency.objects.filter(currency_type=Currency.CurrencyType.FIAT).count()
83
- crypto_count = Currency.objects.filter(currency_type=Currency.CurrencyType.CRYPTO).count()
84
-
85
- active_fiat = Currency.objects.filter(
86
- currency_type=Currency.CurrencyType.FIAT,
87
- ).count()
88
- active_crypto = Currency.objects.filter(
89
- currency_type=Currency.CurrencyType.CRYPTO,
90
- ).count()
91
-
92
- self.stdout.write(f"\n📈 Overview:")
93
- self.stdout.write(f" Total currencies: {total}")
94
- self.stdout.write(f" Active: {active} | Inactive: {inactive}")
95
- self.stdout.write(f" Fiat: {fiat_count} ({active_fiat} active)")
96
- self.stdout.write(f" Crypto: {crypto_count} ({active_crypto} active)")
97
-
98
- # Rate update status
99
- now = timezone.now()
119
+ total_currencies = Currency.objects.count()
120
+ active_currencies = Currency.objects.filter(is_active=True).count()
121
+ crypto_currencies = Currency.objects.filter(currency_type='crypto').count()
122
+ fiat_currencies = Currency.objects.filter(currency_type='fiat').count()
123
+
124
+ # Network stats
125
+ total_networks = Network.objects.count()
126
+ active_networks = Network.objects.filter(is_active=True).count()
127
+
128
+ # Provider currency stats
129
+ total_provider_currencies = ProviderCurrency.objects.count()
130
+ active_provider_currencies = ProviderCurrency.objects.filter(is_active=True).count()
131
+
132
+ # Payment stats
133
+ total_payments = UniversalPayment.objects.count()
134
+ completed_payments = UniversalPayment.objects.filter(status='completed').count()
135
+
136
+ self.stdout.write(self.style.SUCCESS("📈 GENERAL STATISTICS"))
137
+ self.stdout.write("-" * 40)
138
+
139
+ stats = [
140
+ ("Total Currencies", total_currencies),
141
+ ("Active Currencies", active_currencies),
142
+ ("Cryptocurrency", crypto_currencies),
143
+ ("Fiat Currency", fiat_currencies),
144
+ ("Networks", f"{active_networks}/{total_networks}"),
145
+ ("Provider Currencies", f"{active_provider_currencies}/{total_provider_currencies}"),
146
+ ("Total Payments", total_payments),
147
+ ("Completed Payments", completed_payments),
148
+ ]
100
149
 
101
- # Recent (last 24h)
102
- recent_threshold = now - timedelta(hours=24)
103
- recent_updates = Currency.objects.filter(
104
- rate_updated_at__gte=recent_threshold
105
- ).count()
150
+ for label, value in stats:
151
+ self.stdout.write(f"{label:<20}: {self.style.WARNING(str(value))}")
106
152
 
107
- # Outdated (older than 7 days)
108
- outdated_threshold = now - timedelta(days=7)
109
- outdated = Currency.objects.filter(
110
- Q(rate_updated_at__lt=outdated_threshold) | Q(rate_updated_at__isnull=True)
111
- ).count()
112
-
113
- self.stdout.write(f"\n🕒 Rate Updates:")
114
- self.stdout.write(f" Updated in last 24h: {recent_updates}")
115
- self.stdout.write(f" Outdated (>7 days): {outdated}")
116
-
117
- # Top cryptocurrencies by USD value
118
- top_crypto = Currency.objects.filter(
119
- currency_type=Currency.CurrencyType.CRYPTO,
120
- ).order_by('-usd_rate')[:options['top']]
121
-
122
- if top_crypto:
123
- self.stdout.write(f"\n🚀 Top {options['top']} Cryptocurrencies by USD Rate:")
124
- for i, currency in enumerate(top_crypto, 1):
125
- age = self._get_rate_age(currency)
126
- self.stdout.write(
127
- f" {i}. {currency.code}: ${currency.usd_rate:,.6f} {age}"
128
- )
129
-
130
- # Major fiat currencies
131
- major_fiat = Currency.objects.filter(
132
- currency_type=Currency.CurrencyType.FIAT,
133
- code__in=['USD', 'EUR', 'GBP', 'JPY', 'CNY'],
134
- ).order_by('code')
135
-
136
- if major_fiat:
137
- self.stdout.write(f"\n💵 Major Fiat Currencies:")
138
- for currency in major_fiat:
139
- age = self._get_rate_age(currency)
140
- self.stdout.write(
141
- f" • {currency.code}: {currency.name} = ${currency.usd_rate:.6f} {age}"
142
- )
153
+ self.stdout.write("")
143
154
 
144
- def _show_detailed_stats(self, options):
145
- """Show detailed statistics."""
146
-
147
- self.stdout.write(f"\n📊 Detailed Statistics:")
148
-
149
- # Decimal places distribution
150
- decimal_stats = Currency.objects.values('decimal_places').annotate(
151
- count=Count('decimal_places')
152
- ).order_by('decimal_places')
153
-
154
- self.stdout.write(f"\n🔢 Decimal Places Distribution:")
155
- for stat in decimal_stats:
156
- self.stdout.write(f" {stat['decimal_places']} places: {stat['count']} currencies")
157
-
158
- # Average rates by type
159
- crypto_avg = Currency.objects.filter(
160
- currency_type=Currency.CurrencyType.CRYPTO,
161
- ).aggregate(avg_rate=Avg('usd_rate'))['avg_rate']
162
-
163
- fiat_avg = Currency.objects.filter(
164
- currency_type=Currency.CurrencyType.FIAT,
165
- ).aggregate(avg_rate=Avg('usd_rate'))['avg_rate']
166
-
167
- self.stdout.write(f"\n📊 Average USD Rates:")
168
- if crypto_avg:
169
- self.stdout.write(f" Cryptocurrencies: ${crypto_avg:.6f}")
170
- if fiat_avg:
171
- self.stdout.write(f" Fiat currencies: ${fiat_avg:.6f}")
172
-
173
- # Min payment amounts
174
- # Note: min_payment_amount field was removed - now handled at provider level
175
- self.stdout.write(f"\n💰 Payment amounts now managed at provider level (ProviderCurrency)")
176
-
177
- # Rate freshness distribution
178
- now = timezone.now()
179
- thresholds = [
180
- ('Last hour', timedelta(hours=1)),
181
- ('Last 24 hours', timedelta(hours=24)),
182
- ('Last week', timedelta(days=7)),
183
- ('Last month', timedelta(days=30)),
184
- ]
185
-
186
- self.stdout.write(f"\n⏰ Rate Update Distribution:")
187
- previous_count = 0
188
- for label, delta in thresholds:
189
- threshold = now - delta
190
- count = Currency.objects.filter(rate_updated_at__gte=threshold).count()
191
- new_in_period = count - previous_count
192
- self.stdout.write(f" {label}: {new_in_period} new updates ({count} total)")
193
- previous_count = count
194
-
195
- # Never updated
196
- never_updated = Currency.objects.filter(rate_updated_at__isnull=True).count()
197
- if never_updated > 0:
198
- self.stdout.write(f" Never updated: {never_updated} currencies")
155
+ def show_detailed_stats(self):
156
+ """Show detailed statistics breakdown."""
157
+ self.stdout.write(self.style.SUCCESS("🔍 DETAILED BREAKDOWN"))
158
+ self.stdout.write("-" * 40)
159
+
160
+ # Currency type breakdown
161
+ crypto_active = Currency.objects.filter(currency_type='crypto', is_active=True).count()
162
+ crypto_inactive = Currency.objects.filter(currency_type='crypto', is_active=False).count()
163
+ fiat_active = Currency.objects.filter(currency_type='fiat', is_active=True).count()
164
+ fiat_inactive = Currency.objects.filter(currency_type='fiat', is_active=False).count()
165
+
166
+ self.stdout.write("Currency Status:")
167
+ self.stdout.write(f" Crypto: {crypto_active} active, {crypto_inactive} inactive")
168
+ self.stdout.write(f" Fiat: {fiat_active} active, {fiat_inactive} inactive")
169
+
170
+ # Provider coverage
171
+ providers = get_provider_registry().get_available_providers()
172
+ self.stdout.write(f"\nProvider Coverage:")
173
+
174
+ for provider_name in providers:
175
+ provider_currencies = ProviderCurrency.objects.filter(
176
+ provider=provider_name,
177
+ is_active=True
178
+ ).count()
179
+ self.stdout.write(f" {provider_name}: {provider_currencies} currencies")
180
+
181
+ # Payment volume by currency
182
+ payment_stats = UniversalPayment.objects.filter(
183
+ status='completed'
184
+ ).values('currency__code').annotate(
185
+ count=Count('id'),
186
+ total_volume=Sum('amount_usd')
187
+ ).order_by('-total_volume')[:5]
188
+
189
+ if payment_stats:
190
+ self.stdout.write(f"\nTop Payment Currencies:")
191
+ for stat in payment_stats:
192
+ currency = stat['currency__code'] or 'Unknown'
193
+ count = stat['count']
194
+ volume = stat['total_volume'] or 0
195
+ self.stdout.write(f" {currency}: {count} payments, ${intcomma(volume)}")
196
+
197
+ self.stdout.write("")
199
198
 
200
- def _check_rate_freshness(self):
199
+ def show_top_currencies(self):
200
+ """Show top currencies by various metrics."""
201
+ top_count = self.options['top']
202
+
203
+ self.stdout.write(self.style.SUCCESS(f"🏆 TOP {top_count} CURRENCIES"))
204
+ self.stdout.write("-" * 40)
205
+
206
+ # Top by payment count
207
+ top_by_payments = UniversalPayment.objects.filter(
208
+ status='completed'
209
+ ).values('currency__code', 'currency__name').annotate(
210
+ payment_count=Count('id')
211
+ ).order_by('-payment_count')[:top_count]
212
+
213
+ if top_by_payments:
214
+ self.stdout.write("By Payment Count:")
215
+ for i, currency in enumerate(top_by_payments, 1):
216
+ code = currency['currency__code'] or 'Unknown'
217
+ name = currency['currency__name'] or 'Unknown'
218
+ count = currency['payment_count']
219
+ self.stdout.write(f" {i:2d}. {code} ({name}): {count} payments")
220
+
221
+ # Top by volume
222
+ top_by_volume = UniversalPayment.objects.filter(
223
+ status='completed'
224
+ ).values('currency__code', 'currency__name').annotate(
225
+ total_volume=Sum('amount_usd')
226
+ ).order_by('-total_volume')[:top_count]
227
+
228
+ if top_by_volume:
229
+ self.stdout.write(f"\nBy Payment Volume:")
230
+ for i, currency in enumerate(top_by_volume, 1):
231
+ code = currency['currency__code'] or 'Unknown'
232
+ name = currency['currency__name'] or 'Unknown'
233
+ volume = currency['total_volume'] or 0
234
+ self.stdout.write(f" {i:2d}. {code} ({name}): ${intcomma(volume)}")
235
+
236
+ self.stdout.write("")
237
+
238
+ def check_rate_freshness(self):
201
239
  """Check for outdated exchange rates."""
202
-
203
- self.stdout.write(f"\n🔍 Rate Freshness Check:")
240
+ self.stdout.write(self.style.SUCCESS("🕐 RATE FRESHNESS CHECK"))
241
+ self.stdout.write("-" * 40)
204
242
 
205
243
  now = timezone.now()
244
+ stale_threshold = now - timedelta(hours=24)
245
+ very_stale_threshold = now - timedelta(days=7)
246
+
247
+ # Check currencies with stale rates
248
+ stale_currencies = Currency.objects.filter(
249
+ updated_at__lt=stale_threshold,
250
+ is_active=True
251
+ ).order_by('updated_at')
252
+
253
+ very_stale_currencies = Currency.objects.filter(
254
+ updated_at__lt=very_stale_threshold,
255
+ is_active=True
256
+ ).order_by('updated_at')
257
+
258
+ fresh_currencies = Currency.objects.filter(
259
+ updated_at__gte=stale_threshold,
260
+ is_active=True
261
+ ).count()
206
262
 
207
- # Very outdated (>30 days)
208
- very_old_threshold = now - timedelta(days=30)
209
- very_old = Currency.objects.filter(
210
- Q(rate_updated_at__lt=very_old_threshold) | Q(rate_updated_at__isnull=True),
211
- )
212
-
213
- if very_old.exists():
214
- self.stdout.write(
215
- self.style.ERROR(f" ❌ {very_old.count()} currencies with very old rates (>30 days)")
216
- )
217
- for currency in very_old[:5]:
218
- age = self._get_rate_age(currency)
219
- self.stdout.write(f" • {currency.code}: {age}")
220
- if very_old.count() > 5:
221
- self.stdout.write(f" ... and {very_old.count() - 5} more")
222
-
223
- # Moderately outdated (7-30 days)
224
- old_threshold = now - timedelta(days=7)
225
- old_currencies = Currency.objects.filter(
226
- rate_updated_at__lt=old_threshold,
227
- rate_updated_at__gte=very_old_threshold,
228
- )
229
-
230
- if old_currencies.exists():
231
- self.stdout.write(
232
- self.style.WARNING(f" ⚠️ {old_currencies.count()} currencies with old rates (7-30 days)")
233
- )
263
+ self.stdout.write(f"Fresh rates (< 24h): {self.style.SUCCESS(fresh_currencies)}")
264
+ self.stdout.write(f"Stale rates (> 24h): {self.style.WARNING(stale_currencies.count())}")
265
+ self.stdout.write(f"Very stale (> 7d): {self.style.ERROR(very_stale_currencies.count())}")
234
266
 
235
- # Fresh rates (last 24h)
236
- fresh_threshold = now - timedelta(hours=24)
237
- fresh = Currency.objects.filter(
238
- rate_updated_at__gte=fresh_threshold,
239
- ).count()
267
+ if very_stale_currencies.exists():
268
+ self.stdout.write(f"\n{self.style.ERROR('⚠️ VERY STALE CURRENCIES:')}")
269
+ for currency in very_stale_currencies[:10]:
270
+ age = now - currency.updated_at
271
+ self.stdout.write(f" {currency.code}: {age.days} days old")
240
272
 
241
- if fresh > 0:
242
- self.stdout.write(
243
- self.style.SUCCESS(f" ✅ {fresh} currencies with fresh rates (<24h)")
244
- )
273
+ if stale_currencies.exists() and not very_stale_currencies.exists():
274
+ self.stdout.write(f"\n{self.style.WARNING('⚠️ STALE CURRENCIES:')}")
275
+ for currency in stale_currencies[:10]:
276
+ age = now - currency.updated_at
277
+ hours = int(age.total_seconds() / 3600)
278
+ self.stdout.write(f" {currency.code}: {hours} hours old")
245
279
 
246
- # Recommendations
247
- total_active = Currency.objects.count()
248
- if very_old.count() > 0:
249
- self.stdout.write(f"\n💡 Recommendations:")
250
- self.stdout.write(f" • Run: python manage.py update_currencies --force-update")
251
- self.stdout.write(f" • Consider deactivating currencies with very old rates")
280
+ self.stdout.write("")
252
281
 
253
- def _get_rate_age(self, currency) -> str:
254
- """Get human-readable age of currency rate."""
255
- if not currency.rate_updated_at:
256
- return "(never updated)"
257
-
258
- age = timezone.now() - currency.rate_updated_at
259
-
260
- if age.days > 30:
261
- return f"({age.days} days ago)"
262
- elif age.days > 0:
263
- return f"({age.days}d ago)"
264
- elif age.seconds > 3600:
265
- hours = age.seconds // 3600
266
- return f"({hours}h ago)"
267
- else:
268
- minutes = age.seconds // 60
269
- return f"({minutes}m ago)"
282
+ def show_provider_stats(self, provider_name: str):
283
+ """Show statistics for specific provider."""
284
+ self.stdout.write(self.style.SUCCESS(f"🏢 PROVIDER STATISTICS: {provider_name.upper()}"))
285
+ self.stdout.write("-" * 40)
286
+
287
+ # Provider currency stats
288
+ provider_currencies = ProviderCurrency.objects.filter(provider=provider_name)
289
+ active_provider_currencies = provider_currencies.filter(is_active=True)
290
+
291
+ # Payment stats for this provider
292
+ provider_payments = UniversalPayment.objects.filter(provider=provider_name)
293
+ completed_payments = provider_payments.filter(status='completed')
294
+
295
+ # Volume stats
296
+ total_volume = completed_payments.aggregate(Sum('amount_usd'))['amount_usd__sum'] or 0
297
+ avg_payment = completed_payments.aggregate(Avg('amount_usd'))['amount_usd__avg'] or 0
298
+
299
+ stats = [
300
+ ("Total Currencies", provider_currencies.count()),
301
+ ("Active Currencies", active_provider_currencies.count()),
302
+ ("Total Payments", provider_payments.count()),
303
+ ("Completed Payments", completed_payments.count()),
304
+ ("Total Volume", f"${intcomma(total_volume)}"),
305
+ ("Average Payment", f"${intcomma(avg_payment):.2f}"),
306
+ ]
307
+
308
+ for label, value in stats:
309
+ self.stdout.write(f"{label:<20}: {self.style.WARNING(str(value))}")
310
+
311
+ # Top currencies for this provider
312
+ top_currencies = completed_payments.values(
313
+ 'currency__code', 'currency__name'
314
+ ).annotate(
315
+ count=Count('id'),
316
+ volume=Sum('amount_usd')
317
+ ).order_by('-volume')[:5]
318
+
319
+ if top_currencies:
320
+ self.stdout.write(f"\nTop Currencies:")
321
+ for currency in top_currencies:
322
+ code = currency['currency__code'] or 'Unknown'
323
+ count = currency['count']
324
+ volume = currency['volume'] or 0
325
+ self.stdout.write(f" {code}: {count} payments, ${intcomma(volume)}")
326
+
327
+ self.stdout.write("")
270
328
 
271
- def _export_to_csv(self, filename: str):
272
- """Export currency data to CSV file."""
329
+ def export_to_csv(self, filename: str):
330
+ """Export statistics to CSV file."""
273
331
  import csv
274
-
275
- self.stdout.write(f"\n📁 Exporting to {filename}...")
332
+ from pathlib import Path
276
333
 
277
334
  try:
335
+ # Collect all statistics
336
+ currencies = Currency.objects.select_related().all()
337
+
278
338
  with open(filename, 'w', newline='', encoding='utf-8') as csvfile:
279
339
  writer = csv.writer(csvfile)
280
340
 
281
- # Header
341
+ # Write header
282
342
  writer.writerow([
283
- 'code', 'name', 'currency_type', 'usd_rate', 'rate_updated_at'
343
+ 'Code', 'Name', 'Type', 'Active', 'Created', 'Updated',
344
+ 'Payment Count', 'Total Volume USD'
284
345
  ])
285
346
 
286
- # Data
287
- currencies = Currency.objects.all().order_by('code')
347
+ # Write currency data
288
348
  for currency in currencies:
349
+ # Get payment stats for this currency
350
+ payments = UniversalPayment.objects.filter(
351
+ currency=currency,
352
+ status='completed'
353
+ )
354
+ payment_count = payments.count()
355
+ total_volume = payments.aggregate(Sum('amount_usd'))['amount_usd__sum'] or 0
356
+
289
357
  writer.writerow([
290
358
  currency.code,
291
359
  currency.name,
292
360
  currency.currency_type,
293
- currency.usd_rate,
294
- currency.rate_updated_at.isoformat() if currency.rate_updated_at else None
361
+ currency.is_active,
362
+ currency.created_at.strftime('%Y-%m-%d'),
363
+ currency.updated_at.strftime('%Y-%m-%d'),
364
+ payment_count,
365
+ f"{total_volume:.2f}"
295
366
  ])
296
367
 
297
368
  self.stdout.write(
298
- self.style.SUCCESS(f" Exported {currencies.count()} currencies to {filename}")
369
+ self.style.SUCCESS(f"✅ Statistics exported to: {filename}")
299
370
  )
300
371
 
301
372
  except Exception as e:
373
+ logger.error(f"Failed to export CSV: {e}")
302
374
  self.stdout.write(
303
- self.style.ERROR(f" Export failed: {str(e)}")
375
+ self.style.ERROR(f"❌ Failed to export CSV: {e}")
304
376
  )