django-cfg 1.2.29__py3-none-any.whl → 1.3.1__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 (258) 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 -9
  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 +600 -108
  9. django_cfg/apps/payments/admin/filters.py +306 -199
  10. django_cfg/apps/payments/admin/payments_admin.py +470 -64
  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/manage_currencies.py +381 -0
  39. django_cfg/apps/payments/management/commands/manage_providers.py +408 -0
  40. django_cfg/apps/payments/middleware/__init__.py +3 -1
  41. django_cfg/apps/payments/middleware/api_access.py +329 -222
  42. django_cfg/apps/payments/middleware/rate_limiting.py +343 -163
  43. django_cfg/apps/payments/middleware/usage_tracking.py +250 -238
  44. django_cfg/apps/payments/migrations/0001_initial.py +708 -536
  45. django_cfg/apps/payments/models/__init__.py +16 -20
  46. django_cfg/apps/payments/models/api_keys.py +121 -43
  47. django_cfg/apps/payments/models/balance.py +150 -115
  48. django_cfg/apps/payments/models/base.py +68 -15
  49. django_cfg/apps/payments/models/currencies.py +207 -67
  50. django_cfg/apps/payments/models/managers/__init__.py +44 -0
  51. django_cfg/apps/payments/models/managers/api_key_managers.py +329 -0
  52. django_cfg/apps/payments/models/managers/balance_managers.py +599 -0
  53. django_cfg/apps/payments/models/managers/currency_managers.py +385 -0
  54. django_cfg/apps/payments/models/managers/payment_managers.py +511 -0
  55. django_cfg/apps/payments/models/managers/subscription_managers.py +641 -0
  56. django_cfg/apps/payments/models/payments.py +235 -284
  57. django_cfg/apps/payments/models/subscriptions.py +257 -177
  58. django_cfg/apps/payments/models/tariffs.py +147 -40
  59. django_cfg/apps/payments/services/__init__.py +209 -56
  60. django_cfg/apps/payments/services/cache/__init__.py +6 -6
  61. django_cfg/apps/payments/services/cache/{simple_cache.py → cache_service.py} +112 -12
  62. django_cfg/apps/payments/services/core/__init__.py +10 -6
  63. django_cfg/apps/payments/services/core/balance_service.py +435 -360
  64. django_cfg/apps/payments/services/core/base.py +166 -0
  65. django_cfg/apps/payments/services/core/currency_service.py +478 -0
  66. django_cfg/apps/payments/services/core/payment_service.py +344 -468
  67. django_cfg/apps/payments/services/core/subscription_service.py +425 -484
  68. django_cfg/apps/payments/services/core/webhook_service.py +410 -0
  69. django_cfg/apps/payments/services/integrations/__init__.py +29 -0
  70. django_cfg/apps/payments/services/integrations/ngrok_service.py +47 -0
  71. django_cfg/apps/payments/services/integrations/providers_config.py +107 -0
  72. django_cfg/apps/payments/services/providers/__init__.py +9 -14
  73. django_cfg/apps/payments/services/providers/base.py +232 -71
  74. django_cfg/apps/payments/services/providers/nowpayments.py +404 -219
  75. django_cfg/apps/payments/services/providers/registry.py +429 -80
  76. django_cfg/apps/payments/services/types/__init__.py +78 -0
  77. django_cfg/apps/payments/services/types/data.py +177 -0
  78. django_cfg/apps/payments/services/types/requests.py +150 -0
  79. django_cfg/apps/payments/services/types/responses.py +156 -0
  80. django_cfg/apps/payments/services/types/webhooks.py +232 -0
  81. django_cfg/apps/payments/signals/__init__.py +33 -8
  82. django_cfg/apps/payments/signals/api_key_signals.py +211 -130
  83. django_cfg/apps/payments/signals/balance_signals.py +174 -0
  84. django_cfg/apps/payments/signals/payment_signals.py +129 -98
  85. django_cfg/apps/payments/signals/subscription_signals.py +195 -143
  86. django_cfg/apps/payments/static/payments/css/components.css +380 -0
  87. django_cfg/apps/payments/static/payments/css/dashboard.css +188 -0
  88. django_cfg/apps/payments/static/payments/js/components.js +545 -0
  89. django_cfg/apps/payments/static/payments/js/utils.js +412 -0
  90. django_cfg/apps/payments/templatetags/__init__.py +1 -1
  91. django_cfg/apps/payments/templatetags/payment_tags.py +466 -0
  92. django_cfg/apps/payments/urls.py +46 -47
  93. django_cfg/apps/payments/urls_admin.py +49 -0
  94. django_cfg/apps/payments/views/api/__init__.py +101 -0
  95. django_cfg/apps/payments/views/api/api_keys.py +387 -0
  96. django_cfg/apps/payments/views/api/balances.py +381 -0
  97. django_cfg/apps/payments/views/api/base.py +298 -0
  98. django_cfg/apps/payments/views/api/currencies.py +402 -0
  99. django_cfg/apps/payments/views/api/payments.py +415 -0
  100. django_cfg/apps/payments/views/api/subscriptions.py +475 -0
  101. django_cfg/apps/payments/views/api/webhooks.py +476 -0
  102. django_cfg/apps/payments/views/serializers/__init__.py +99 -0
  103. django_cfg/apps/payments/views/serializers/api_keys.py +424 -0
  104. django_cfg/apps/payments/views/serializers/balances.py +300 -0
  105. django_cfg/apps/payments/views/serializers/currencies.py +335 -0
  106. django_cfg/apps/payments/views/serializers/payments.py +387 -0
  107. django_cfg/apps/payments/views/serializers/subscriptions.py +429 -0
  108. django_cfg/apps/payments/views/serializers/webhooks.py +137 -0
  109. django_cfg/apps/tasks/urls.py +0 -2
  110. django_cfg/apps/tasks/urls_admin.py +14 -0
  111. django_cfg/apps/urls.py +4 -4
  112. django_cfg/config.py +1 -1
  113. django_cfg/core/config.py +75 -4
  114. django_cfg/core/generation.py +25 -4
  115. django_cfg/core/integration/README.md +363 -0
  116. django_cfg/core/integration/__init__.py +47 -0
  117. django_cfg/core/integration/commands_collector.py +239 -0
  118. django_cfg/core/integration/display/__init__.py +15 -0
  119. django_cfg/core/integration/display/base.py +157 -0
  120. django_cfg/core/integration/display/ngrok.py +164 -0
  121. django_cfg/core/integration/display/startup.py +815 -0
  122. django_cfg/core/integration/url_integration.py +123 -0
  123. django_cfg/core/integration/version_checker.py +160 -0
  124. django_cfg/management/commands/auto_generate.py +4 -0
  125. django_cfg/management/commands/check_settings.py +6 -0
  126. django_cfg/management/commands/clear_constance.py +5 -2
  127. django_cfg/management/commands/create_token.py +6 -0
  128. django_cfg/management/commands/list_urls.py +6 -0
  129. django_cfg/management/commands/migrate_all.py +6 -0
  130. django_cfg/management/commands/migrator.py +3 -0
  131. django_cfg/management/commands/rundramatiq.py +6 -0
  132. django_cfg/management/commands/runserver_ngrok.py +51 -29
  133. django_cfg/management/commands/script.py +6 -0
  134. django_cfg/management/commands/show_config.py +12 -2
  135. django_cfg/management/commands/show_urls.py +4 -0
  136. django_cfg/management/commands/superuser.py +6 -0
  137. django_cfg/management/commands/task_clear.py +4 -1
  138. django_cfg/management/commands/task_status.py +3 -1
  139. django_cfg/management/commands/test_email.py +3 -0
  140. django_cfg/management/commands/test_telegram.py +6 -0
  141. django_cfg/management/commands/test_twilio.py +6 -0
  142. django_cfg/management/commands/tree.py +6 -0
  143. django_cfg/management/commands/validate_config.py +155 -149
  144. django_cfg/models/constance.py +31 -11
  145. django_cfg/models/payments.py +175 -498
  146. django_cfg/modules/django_currency/__init__.py +16 -11
  147. django_cfg/modules/django_currency/clients/__init__.py +4 -4
  148. django_cfg/modules/django_currency/clients/coinpaprika_client.py +289 -0
  149. django_cfg/modules/django_currency/clients/yahoo_client.py +157 -0
  150. django_cfg/modules/django_currency/core/__init__.py +1 -7
  151. django_cfg/modules/django_currency/core/converter.py +18 -23
  152. django_cfg/modules/django_currency/core/models.py +122 -11
  153. django_cfg/modules/django_currency/database/__init__.py +4 -4
  154. django_cfg/modules/django_currency/database/database_loader.py +190 -309
  155. django_cfg/modules/django_logger.py +160 -146
  156. django_cfg/modules/django_unfold/dashboard.py +65 -12
  157. django_cfg/registry/core.py +1 -0
  158. django_cfg/template_archive/django_sample.zip +0 -0
  159. django_cfg/templates/admin/components/action_grid.html +9 -9
  160. django_cfg/templates/admin/components/metric_card.html +5 -5
  161. django_cfg/templates/admin/components/status_badge.html +2 -2
  162. django_cfg/templates/admin/layouts/dashboard_with_tabs.html +152 -24
  163. django_cfg/templates/admin/snippets/components/quick_actions.html +3 -3
  164. django_cfg/templates/admin/snippets/components/system_health.html +1 -1
  165. django_cfg/templates/admin/snippets/tabs/overview_tab.html +49 -52
  166. django_cfg/utils/smart_defaults.py +222 -571
  167. django_cfg/utils/toolkit.py +51 -11
  168. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/METADATA +5 -4
  169. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/RECORD +172 -182
  170. django_cfg/apps/payments/__init__.py +0 -8
  171. django_cfg/apps/payments/admin/tariffs_admin.py +0 -199
  172. django_cfg/apps/payments/config/module.py +0 -70
  173. django_cfg/apps/payments/config/providers.py +0 -105
  174. django_cfg/apps/payments/config/settings.py +0 -96
  175. django_cfg/apps/payments/config/utils.py +0 -52
  176. django_cfg/apps/payments/decorators.py +0 -291
  177. django_cfg/apps/payments/management/commands/README.md +0 -178
  178. django_cfg/apps/payments/management/commands/currency_stats.py +0 -323
  179. django_cfg/apps/payments/management/commands/populate_currencies.py +0 -246
  180. django_cfg/apps/payments/management/commands/update_currencies.py +0 -336
  181. django_cfg/apps/payments/managers/__init__.py +0 -22
  182. django_cfg/apps/payments/managers/api_key_manager.py +0 -35
  183. django_cfg/apps/payments/managers/balance_manager.py +0 -361
  184. django_cfg/apps/payments/managers/currency_manager.py +0 -83
  185. django_cfg/apps/payments/managers/payment_manager.py +0 -44
  186. django_cfg/apps/payments/managers/subscription_manager.py +0 -37
  187. django_cfg/apps/payments/managers/tariff_manager.py +0 -29
  188. django_cfg/apps/payments/models/events.py +0 -73
  189. django_cfg/apps/payments/serializers/__init__.py +0 -56
  190. django_cfg/apps/payments/serializers/api_keys.py +0 -51
  191. django_cfg/apps/payments/serializers/balance.py +0 -59
  192. django_cfg/apps/payments/serializers/currencies.py +0 -55
  193. django_cfg/apps/payments/serializers/payments.py +0 -62
  194. django_cfg/apps/payments/serializers/subscriptions.py +0 -71
  195. django_cfg/apps/payments/serializers/tariffs.py +0 -56
  196. django_cfg/apps/payments/services/billing/__init__.py +0 -8
  197. django_cfg/apps/payments/services/cache/base.py +0 -30
  198. django_cfg/apps/payments/services/core/fallback_service.py +0 -432
  199. django_cfg/apps/payments/services/internal_types.py +0 -297
  200. django_cfg/apps/payments/services/middleware/__init__.py +0 -8
  201. django_cfg/apps/payments/services/monitoring/__init__.py +0 -22
  202. django_cfg/apps/payments/services/monitoring/api_schemas.py +0 -222
  203. django_cfg/apps/payments/services/monitoring/provider_health.py +0 -372
  204. django_cfg/apps/payments/services/providers/cryptapi.py +0 -273
  205. django_cfg/apps/payments/services/providers/cryptomus.py +0 -311
  206. django_cfg/apps/payments/services/security/__init__.py +0 -34
  207. django_cfg/apps/payments/services/security/error_handler.py +0 -637
  208. django_cfg/apps/payments/services/security/payment_notifications.py +0 -342
  209. django_cfg/apps/payments/services/security/webhook_validator.py +0 -475
  210. django_cfg/apps/payments/services/validators/__init__.py +0 -8
  211. django_cfg/apps/payments/static/payments/css/payments.css +0 -340
  212. django_cfg/apps/payments/static/payments/js/notifications.js +0 -202
  213. django_cfg/apps/payments/static/payments/js/payment-utils.js +0 -318
  214. django_cfg/apps/payments/static/payments/js/theme.js +0 -86
  215. django_cfg/apps/payments/tasks/__init__.py +0 -12
  216. django_cfg/apps/payments/tasks/webhook_processing.py +0 -177
  217. django_cfg/apps/payments/templates/payments/base.html +0 -182
  218. django_cfg/apps/payments/templates/payments/components/payment_card.html +0 -201
  219. django_cfg/apps/payments/templates/payments/components/payment_qr_code.html +0 -109
  220. django_cfg/apps/payments/templates/payments/components/progress_bar.html +0 -36
  221. django_cfg/apps/payments/templates/payments/components/provider_stats.html +0 -40
  222. django_cfg/apps/payments/templates/payments/components/status_badge.html +0 -27
  223. django_cfg/apps/payments/templates/payments/components/status_overview.html +0 -144
  224. django_cfg/apps/payments/templates/payments/dashboard.html +0 -346
  225. django_cfg/apps/payments/templatetags/payments_tags.py +0 -315
  226. django_cfg/apps/payments/urls_templates.py +0 -52
  227. django_cfg/apps/payments/utils/__init__.py +0 -45
  228. django_cfg/apps/payments/utils/billing_utils.py +0 -342
  229. django_cfg/apps/payments/utils/config_utils.py +0 -245
  230. django_cfg/apps/payments/utils/middleware_utils.py +0 -228
  231. django_cfg/apps/payments/utils/validation_utils.py +0 -94
  232. django_cfg/apps/payments/views/__init__.py +0 -62
  233. django_cfg/apps/payments/views/api_key_views.py +0 -164
  234. django_cfg/apps/payments/views/balance_views.py +0 -75
  235. django_cfg/apps/payments/views/currency_views.py +0 -111
  236. django_cfg/apps/payments/views/payment_views.py +0 -149
  237. django_cfg/apps/payments/views/subscription_views.py +0 -135
  238. django_cfg/apps/payments/views/tariff_views.py +0 -131
  239. django_cfg/apps/payments/views/templates/__init__.py +0 -25
  240. django_cfg/apps/payments/views/templates/ajax.py +0 -312
  241. django_cfg/apps/payments/views/templates/base.py +0 -204
  242. django_cfg/apps/payments/views/templates/dashboard.py +0 -60
  243. django_cfg/apps/payments/views/templates/payment_detail.py +0 -102
  244. django_cfg/apps/payments/views/templates/payment_management.py +0 -164
  245. django_cfg/apps/payments/views/templates/qr_code.py +0 -174
  246. django_cfg/apps/payments/views/templates/stats.py +0 -240
  247. django_cfg/apps/payments/views/templates/utils.py +0 -181
  248. django_cfg/apps/payments/views/webhook_views.py +0 -266
  249. django_cfg/apps/payments/viewsets.py +0 -65
  250. django_cfg/core/integration.py +0 -160
  251. django_cfg/modules/django_currency/clients/coingecko_client.py +0 -257
  252. django_cfg/modules/django_currency/clients/yfinance_client.py +0 -246
  253. django_cfg/template_archive/.gitignore +0 -1
  254. django_cfg/template_archive/__init__.py +0 -0
  255. django_cfg/urls.py +0 -33
  256. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/WHEEL +0 -0
  257. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/entry_points.txt +0 -0
  258. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,381 @@
1
+ """
2
+ Currency management command for Universal Payment System v2.0.
3
+
4
+ Integrates with django_currency module for automatic rate updates and population.
5
+ """
6
+
7
+ from django.core.management.base import BaseCommand, CommandError
8
+ from django.db import transaction
9
+ from django.utils import timezone
10
+ from django.db.models import Q
11
+ from datetime import timedelta
12
+ from typing import List, Optional
13
+ import time
14
+
15
+ from django_cfg.modules.django_logger import get_logger
16
+ from django_cfg.modules.django_currency import (
17
+ CurrencyConverter, convert_currency, get_exchange_rate,
18
+ CurrencyError, CurrencyNotFoundError
19
+ )
20
+ from django_cfg.apps.payments.models import Currency, Network, ProviderCurrency
21
+
22
+ logger = get_logger("manage_currencies")
23
+
24
+
25
+ class Command(BaseCommand):
26
+ """
27
+ Universal currency management command using ready modules.
28
+
29
+ Features:
30
+ - Population of missing currencies
31
+ - USD rate updates using django_currency
32
+ - Provider currency synchronization
33
+ - Flexible filtering and options
34
+ """
35
+
36
+ help = 'Manage currencies and exchange rates for the payment system'
37
+
38
+ def add_arguments(self, parser):
39
+ """Add command arguments."""
40
+
41
+ # Main operation modes
42
+ parser.add_argument(
43
+ '--populate',
44
+ action='store_true',
45
+ help='Populate missing base currencies'
46
+ )
47
+
48
+ parser.add_argument(
49
+ '--rates-only',
50
+ action='store_true',
51
+ help='Update USD exchange rates only (no population)'
52
+ )
53
+
54
+ parser.add_argument(
55
+ '--sync-providers',
56
+ action='store_true',
57
+ help='Sync provider currencies after rate updates'
58
+ )
59
+
60
+ # Filtering options
61
+ parser.add_argument(
62
+ '--currency',
63
+ type=str,
64
+ help='Update specific currency code (e.g., BTC, ETH)'
65
+ )
66
+
67
+ parser.add_argument(
68
+ '--currency-type',
69
+ choices=['fiat', 'crypto'],
70
+ help='Filter by currency type'
71
+ )
72
+
73
+ parser.add_argument(
74
+ '--provider',
75
+ type=str,
76
+ help='Filter by provider name'
77
+ )
78
+
79
+ # Behavior options
80
+ parser.add_argument(
81
+ '--force',
82
+ action='store_true',
83
+ help='Force refresh rates even if recently updated'
84
+ )
85
+
86
+ parser.add_argument(
87
+ '--skip-existing',
88
+ action='store_true',
89
+ help='Skip currencies that already exist during population'
90
+ )
91
+
92
+ parser.add_argument(
93
+ '--dry-run',
94
+ action='store_true',
95
+ help='Show what would be done without making changes'
96
+ )
97
+
98
+ parser.add_argument(
99
+ '--limit',
100
+ type=int,
101
+ default=100,
102
+ help='Limit number of currencies to process (default: 100)'
103
+ )
104
+
105
+ def handle(self, *args, **options):
106
+ """Main command handler."""
107
+
108
+ start_time = time.time()
109
+
110
+ try:
111
+ self.stdout.write(
112
+ self.style.SUCCESS('🚀 Starting Universal Currency Management')
113
+ )
114
+
115
+ # Determine operation mode
116
+ if options['populate']:
117
+ result = self._populate_currencies(options)
118
+ elif options['rates_only']:
119
+ result = self._update_rates_only(options)
120
+ else:
121
+ # Default: populate + rates
122
+ self.stdout.write("📋 Running full currency management (populate + rates)")
123
+ populate_result = self._populate_currencies(options)
124
+ rates_result = self._update_rates_only(options)
125
+ result = populate_result + rates_result
126
+
127
+ # Optional provider sync
128
+ if options['sync_providers']:
129
+ self._sync_provider_currencies(options)
130
+
131
+ # Show summary
132
+ elapsed = time.time() - start_time
133
+ self.stdout.write(
134
+ self.style.SUCCESS(
135
+ f'✅ Currency management completed in {elapsed:.1f}s'
136
+ )
137
+ )
138
+
139
+ if not options['dry_run']:
140
+ self._show_final_stats()
141
+
142
+ except Exception as e:
143
+ self.stdout.write(
144
+ self.style.ERROR(f'❌ Currency management failed: {e}')
145
+ )
146
+ logger.error(f"Currency management command failed: {e}")
147
+ raise CommandError(f"Command failed: {e}")
148
+
149
+ def _populate_currencies(self, options) -> int:
150
+ """Populate missing base currencies."""
151
+
152
+ self.stdout.write("📦 Populating base currencies...")
153
+
154
+ # Define standard currencies to populate
155
+ standard_currencies = [
156
+ # Major fiat currencies
157
+ ('USD', 'US Dollar', Currency.CurrencyType.FIAT, '$', 2),
158
+ ('EUR', 'Euro', Currency.CurrencyType.FIAT, '€', 2),
159
+ ('GBP', 'British Pound', Currency.CurrencyType.FIAT, '£', 2),
160
+ ('JPY', 'Japanese Yen', Currency.CurrencyType.FIAT, '¥', 0),
161
+ ('CNY', 'Chinese Yuan', Currency.CurrencyType.FIAT, '¥', 2),
162
+ ('RUB', 'Russian Ruble', Currency.CurrencyType.FIAT, '₽', 2),
163
+
164
+ # Major cryptocurrencies
165
+ ('BTC', 'Bitcoin', Currency.CurrencyType.CRYPTO, '₿', 8),
166
+ ('ETH', 'Ethereum', Currency.CurrencyType.CRYPTO, 'Ξ', 8),
167
+ ('USDT', 'Tether USD', Currency.CurrencyType.CRYPTO, '₮', 6),
168
+ ('USDC', 'USD Coin', Currency.CurrencyType.CRYPTO, '', 6),
169
+ ('BNB', 'Binance Coin', Currency.CurrencyType.CRYPTO, '', 8),
170
+ ('ADA', 'Cardano', Currency.CurrencyType.CRYPTO, '', 6),
171
+ ('SOL', 'Solana', Currency.CurrencyType.CRYPTO, '', 8),
172
+ ('DOT', 'Polkadot', Currency.CurrencyType.CRYPTO, '', 8),
173
+ ('MATIC', 'Polygon', Currency.CurrencyType.CRYPTO, '', 8),
174
+ ('LTC', 'Litecoin', Currency.CurrencyType.CRYPTO, 'Ł', 8),
175
+ ('TRX', 'TRON', Currency.CurrencyType.CRYPTO, '', 6),
176
+ ('XRP', 'Ripple', Currency.CurrencyType.CRYPTO, '', 6),
177
+ ]
178
+
179
+ # Apply currency type filter
180
+ if options['currency_type']:
181
+ currency_type_filter = Currency.CurrencyType.FIAT if options['currency_type'] == 'fiat' else Currency.CurrencyType.CRYPTO
182
+ standard_currencies = [
183
+ c for c in standard_currencies
184
+ if c[2] == currency_type_filter
185
+ ]
186
+
187
+ # Apply specific currency filter
188
+ if options['currency']:
189
+ currency_code = options['currency'].upper()
190
+ standard_currencies = [
191
+ c for c in standard_currencies
192
+ if c[0] == currency_code
193
+ ]
194
+
195
+ if not standard_currencies:
196
+ raise CommandError(f"Currency '{currency_code}' not in standard list")
197
+
198
+ created_count = 0
199
+ skipped_count = 0
200
+
201
+ for code, name, currency_type, symbol, decimal_places in standard_currencies:
202
+
203
+ if options['dry_run']:
204
+ exists = Currency.objects.filter(code=code).exists()
205
+ if exists and options['skip_existing']:
206
+ self.stdout.write(f" [DRY RUN] Would skip existing {code}")
207
+ skipped_count += 1
208
+ else:
209
+ self.stdout.write(f" [DRY RUN] Would create/update {code}")
210
+ continue
211
+
212
+ try:
213
+ currency, created = Currency.objects.get_or_create(
214
+ code=code,
215
+ defaults={
216
+ 'name': name,
217
+ 'currency_type': currency_type,
218
+ 'symbol': symbol,
219
+ 'decimal_places': decimal_places,
220
+ 'is_active': True
221
+ }
222
+ )
223
+
224
+ if created:
225
+ self.stdout.write(f" ✅ Created {code} - {name}")
226
+ created_count += 1
227
+ logger.info(f"Created currency: {code}")
228
+ elif not options['skip_existing']:
229
+ # Update existing currency if not skipping
230
+ currency.name = name
231
+ currency.symbol = symbol
232
+ currency.decimal_places = decimal_places
233
+ currency.save()
234
+ self.stdout.write(f" 🔄 Updated {code} - {name}")
235
+ else:
236
+ self.stdout.write(f" ⏭️ Skipped existing {code}")
237
+ skipped_count += 1
238
+
239
+ except Exception as e:
240
+ self.stdout.write(f" ❌ Failed to create {code}: {e}")
241
+ logger.error(f"Failed to create currency {code}: {e}")
242
+
243
+ self.stdout.write(f"📦 Population complete: {created_count} created, {skipped_count} skipped")
244
+ return created_count
245
+
246
+ def _update_rates_only(self, options) -> int:
247
+ """Update USD exchange rates using django_currency module."""
248
+
249
+ self.stdout.write("💱 Updating USD exchange rates...")
250
+
251
+ # Build queryset based on options
252
+ queryset = Currency.objects.all()
253
+
254
+ if options['currency']:
255
+ queryset = queryset.filter(code__iexact=options['currency'])
256
+ if not queryset.exists():
257
+ raise CommandError(f"Currency '{options['currency']}' not found")
258
+
259
+ if options['currency_type']:
260
+ currency_type = Currency.CurrencyType.FIAT if options['currency_type'] == 'fiat' else Currency.CurrencyType.CRYPTO
261
+ queryset = queryset.filter(currency_type=currency_type)
262
+
263
+ # Filter by staleness unless forced
264
+ if not options['force']:
265
+ stale_threshold = timezone.now() - timedelta(hours=12)
266
+
267
+ # Get currencies that need rate updates through ProviderCurrency
268
+ currencies_needing_update = Currency.objects.filter(
269
+ Q(providercurrency__usd_rate__isnull=True) |
270
+ Q(providercurrency__rate_updated_at__isnull=True) |
271
+ Q(providercurrency__rate_updated_at__lt=stale_threshold)
272
+ ).distinct()
273
+
274
+ queryset = queryset.filter(id__in=currencies_needing_update)
275
+
276
+ # Apply limit
277
+ queryset = queryset[:options['limit']]
278
+
279
+ updated_count = 0
280
+ error_count = 0
281
+
282
+ self.stdout.write(f"📊 Processing {queryset.count()} currencies...")
283
+
284
+ for currency in queryset:
285
+
286
+ if options['dry_run']:
287
+ self.stdout.write(f" [DRY RUN] Would update {currency.code}")
288
+ continue
289
+
290
+ try:
291
+ # Use django_currency module to get rate
292
+ if currency.code == 'USD':
293
+ # USD is the base currency
294
+ usd_rate = 1.0
295
+ else:
296
+ # Get rate from django_currency
297
+ usd_rate = get_exchange_rate(currency.code, 'USD')
298
+
299
+ if usd_rate and usd_rate > 0:
300
+ # Update rate in ProviderCurrency (create if doesn't exist)
301
+ provider_currency, created = ProviderCurrency.objects.get_or_create(
302
+ currency=currency,
303
+ provider_name='system', # System-level rate
304
+ provider_currency_code=currency.code,
305
+ defaults={
306
+ 'usd_rate': usd_rate,
307
+ 'rate_updated_at': timezone.now(),
308
+ 'is_enabled': True,
309
+ 'is_stable': currency.currency_type == Currency.CurrencyType.FIAT
310
+ }
311
+ )
312
+
313
+ if not created:
314
+ provider_currency.usd_rate = usd_rate
315
+ provider_currency.rate_updated_at = timezone.now()
316
+ provider_currency.save(update_fields=['usd_rate', 'rate_updated_at'])
317
+
318
+ # Update currency's exchange rate source
319
+ currency.exchange_rate_source = 'django_currency'
320
+ currency.save(update_fields=['exchange_rate_source'])
321
+
322
+ self.stdout.write(f" ✅ {currency.code}: ${usd_rate:.8f}")
323
+ updated_count += 1
324
+
325
+ else:
326
+ self.stdout.write(f" ⚠️ {currency.code}: No rate available")
327
+
328
+ except (CurrencyError, CurrencyNotFoundError) as e:
329
+ self.stdout.write(f" ⚠️ {currency.code}: {str(e)}")
330
+ error_count += 1
331
+ except Exception as e:
332
+ self.stdout.write(f" ❌ {currency.code}: {str(e)}")
333
+ error_count += 1
334
+ logger.error(f"Failed to update rate for {currency.code}: {e}")
335
+
336
+ self.stdout.write(f"💱 Rate update complete: {updated_count} updated, {error_count} errors")
337
+ return updated_count
338
+
339
+ def _sync_provider_currencies(self, options):
340
+ """Sync provider currencies after rate updates."""
341
+
342
+ self.stdout.write("🔄 Syncing provider currencies...")
343
+
344
+ try:
345
+ from django.core.management import call_command
346
+
347
+ if options['provider']:
348
+ call_command('manage_providers', '--provider', options['provider'])
349
+ else:
350
+ call_command('manage_providers', '--all')
351
+
352
+ self.stdout.write("🔄 Provider sync completed")
353
+
354
+ except Exception as e:
355
+ self.stdout.write(f"⚠️ Provider sync failed: {e}")
356
+ logger.warning(f"Provider sync failed: {e}")
357
+
358
+ def _show_final_stats(self):
359
+ """Show final statistics."""
360
+
361
+ try:
362
+ total_currencies = Currency.objects.count()
363
+ fiat_count = Currency.objects.filter(currency_type=Currency.CurrencyType.FIAT).count()
364
+ crypto_count = Currency.objects.filter(currency_type=Currency.CurrencyType.CRYPTO).count()
365
+ active_count = Currency.objects.filter(is_active=True).count()
366
+
367
+ # Count currencies with rates
368
+ currencies_with_rates = Currency.objects.filter(
369
+ providercurrency__usd_rate__isnull=False
370
+ ).distinct().count()
371
+
372
+ rate_coverage = (currencies_with_rates / total_currencies * 100) if total_currencies > 0 else 0
373
+
374
+ self.stdout.write("\n📊 Final Statistics:")
375
+ self.stdout.write(f" Total currencies: {total_currencies}")
376
+ self.stdout.write(f" Fiat: {fiat_count}, Crypto: {crypto_count}")
377
+ self.stdout.write(f" Active: {active_count}")
378
+ self.stdout.write(f" With USD rates: {currencies_with_rates} ({rate_coverage:.1f}%)")
379
+
380
+ except Exception as e:
381
+ logger.warning(f"Failed to show final stats: {e}")