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.
- django_cfg/__init__.py +1 -1
- django_cfg/apps/api/health/views.py +4 -2
- django_cfg/apps/knowbase/config/settings.py +16 -15
- django_cfg/apps/payments/README.md +326 -0
- django_cfg/apps/payments/admin/__init__.py +20 -9
- django_cfg/apps/payments/admin/api_keys_admin.py +521 -237
- django_cfg/apps/payments/admin/balance_admin.py +592 -297
- django_cfg/apps/payments/admin/currencies_admin.py +600 -108
- django_cfg/apps/payments/admin/filters.py +306 -199
- django_cfg/apps/payments/admin/payments_admin.py +470 -64
- django_cfg/apps/payments/admin/subscriptions_admin.py +578 -128
- django_cfg/apps/payments/admin_interface/__init__.py +18 -0
- django_cfg/apps/payments/admin_interface/templates/payments/base.html +162 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/dev_tool_card.html +38 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/loading_spinner.html +16 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/notification.html +27 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/provider_card.html +86 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/status_card.html +39 -0
- django_cfg/apps/payments/admin_interface/templates/payments/currency_converter.html +382 -0
- django_cfg/apps/payments/admin_interface/templates/payments/payment_dashboard.html +300 -0
- django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +303 -0
- django_cfg/apps/payments/admin_interface/templates/payments/payment_list.html +382 -0
- django_cfg/apps/payments/admin_interface/templates/payments/payment_status.html +500 -0
- django_cfg/apps/payments/admin_interface/templates/payments/webhook_dashboard.html +594 -0
- django_cfg/apps/payments/admin_interface/views/__init__.py +23 -0
- django_cfg/apps/payments/admin_interface/views/payment_views.py +259 -0
- django_cfg/apps/payments/admin_interface/views/webhook_dashboard.py +37 -0
- django_cfg/apps/payments/apps.py +34 -9
- django_cfg/apps/payments/config/__init__.py +28 -51
- django_cfg/apps/payments/config/constance/__init__.py +22 -0
- django_cfg/apps/payments/config/constance/config_service.py +123 -0
- django_cfg/apps/payments/config/constance/fields.py +69 -0
- django_cfg/apps/payments/config/constance/settings.py +160 -0
- django_cfg/apps/payments/config/django_cfg_integration.py +202 -0
- django_cfg/apps/payments/config/helpers.py +130 -0
- django_cfg/apps/payments/management/__init__.py +1 -3
- django_cfg/apps/payments/management/commands/__init__.py +1 -3
- django_cfg/apps/payments/management/commands/manage_currencies.py +381 -0
- django_cfg/apps/payments/management/commands/manage_providers.py +408 -0
- django_cfg/apps/payments/middleware/__init__.py +3 -1
- django_cfg/apps/payments/middleware/api_access.py +329 -222
- django_cfg/apps/payments/middleware/rate_limiting.py +343 -163
- django_cfg/apps/payments/middleware/usage_tracking.py +250 -238
- django_cfg/apps/payments/migrations/0001_initial.py +708 -536
- django_cfg/apps/payments/models/__init__.py +16 -20
- django_cfg/apps/payments/models/api_keys.py +121 -43
- django_cfg/apps/payments/models/balance.py +150 -115
- django_cfg/apps/payments/models/base.py +68 -15
- django_cfg/apps/payments/models/currencies.py +207 -67
- django_cfg/apps/payments/models/managers/__init__.py +44 -0
- django_cfg/apps/payments/models/managers/api_key_managers.py +329 -0
- django_cfg/apps/payments/models/managers/balance_managers.py +599 -0
- django_cfg/apps/payments/models/managers/currency_managers.py +385 -0
- django_cfg/apps/payments/models/managers/payment_managers.py +511 -0
- django_cfg/apps/payments/models/managers/subscription_managers.py +641 -0
- django_cfg/apps/payments/models/payments.py +235 -284
- django_cfg/apps/payments/models/subscriptions.py +257 -177
- django_cfg/apps/payments/models/tariffs.py +147 -40
- django_cfg/apps/payments/services/__init__.py +209 -56
- django_cfg/apps/payments/services/cache/__init__.py +6 -6
- django_cfg/apps/payments/services/cache/{simple_cache.py → cache_service.py} +112 -12
- django_cfg/apps/payments/services/core/__init__.py +10 -6
- django_cfg/apps/payments/services/core/balance_service.py +435 -360
- django_cfg/apps/payments/services/core/base.py +166 -0
- django_cfg/apps/payments/services/core/currency_service.py +478 -0
- django_cfg/apps/payments/services/core/payment_service.py +344 -468
- django_cfg/apps/payments/services/core/subscription_service.py +425 -484
- django_cfg/apps/payments/services/core/webhook_service.py +410 -0
- django_cfg/apps/payments/services/integrations/__init__.py +29 -0
- django_cfg/apps/payments/services/integrations/ngrok_service.py +47 -0
- django_cfg/apps/payments/services/integrations/providers_config.py +107 -0
- django_cfg/apps/payments/services/providers/__init__.py +9 -14
- django_cfg/apps/payments/services/providers/base.py +232 -71
- django_cfg/apps/payments/services/providers/nowpayments.py +404 -219
- django_cfg/apps/payments/services/providers/registry.py +429 -80
- django_cfg/apps/payments/services/types/__init__.py +78 -0
- django_cfg/apps/payments/services/types/data.py +177 -0
- django_cfg/apps/payments/services/types/requests.py +150 -0
- django_cfg/apps/payments/services/types/responses.py +156 -0
- django_cfg/apps/payments/services/types/webhooks.py +232 -0
- django_cfg/apps/payments/signals/__init__.py +33 -8
- django_cfg/apps/payments/signals/api_key_signals.py +211 -130
- django_cfg/apps/payments/signals/balance_signals.py +174 -0
- django_cfg/apps/payments/signals/payment_signals.py +129 -98
- django_cfg/apps/payments/signals/subscription_signals.py +195 -143
- django_cfg/apps/payments/static/payments/css/components.css +380 -0
- django_cfg/apps/payments/static/payments/css/dashboard.css +188 -0
- django_cfg/apps/payments/static/payments/js/components.js +545 -0
- django_cfg/apps/payments/static/payments/js/utils.js +412 -0
- django_cfg/apps/payments/templatetags/__init__.py +1 -1
- django_cfg/apps/payments/templatetags/payment_tags.py +466 -0
- django_cfg/apps/payments/urls.py +46 -47
- django_cfg/apps/payments/urls_admin.py +49 -0
- django_cfg/apps/payments/views/api/__init__.py +101 -0
- django_cfg/apps/payments/views/api/api_keys.py +387 -0
- django_cfg/apps/payments/views/api/balances.py +381 -0
- django_cfg/apps/payments/views/api/base.py +298 -0
- django_cfg/apps/payments/views/api/currencies.py +402 -0
- django_cfg/apps/payments/views/api/payments.py +415 -0
- django_cfg/apps/payments/views/api/subscriptions.py +475 -0
- django_cfg/apps/payments/views/api/webhooks.py +476 -0
- django_cfg/apps/payments/views/serializers/__init__.py +99 -0
- django_cfg/apps/payments/views/serializers/api_keys.py +424 -0
- django_cfg/apps/payments/views/serializers/balances.py +300 -0
- django_cfg/apps/payments/views/serializers/currencies.py +335 -0
- django_cfg/apps/payments/views/serializers/payments.py +387 -0
- django_cfg/apps/payments/views/serializers/subscriptions.py +429 -0
- django_cfg/apps/payments/views/serializers/webhooks.py +137 -0
- django_cfg/apps/tasks/urls.py +0 -2
- django_cfg/apps/tasks/urls_admin.py +14 -0
- django_cfg/apps/urls.py +4 -4
- django_cfg/config.py +1 -1
- django_cfg/core/config.py +75 -4
- django_cfg/core/generation.py +25 -4
- django_cfg/core/integration/README.md +363 -0
- django_cfg/core/integration/__init__.py +47 -0
- django_cfg/core/integration/commands_collector.py +239 -0
- django_cfg/core/integration/display/__init__.py +15 -0
- django_cfg/core/integration/display/base.py +157 -0
- django_cfg/core/integration/display/ngrok.py +164 -0
- django_cfg/core/integration/display/startup.py +815 -0
- django_cfg/core/integration/url_integration.py +123 -0
- django_cfg/core/integration/version_checker.py +160 -0
- django_cfg/management/commands/auto_generate.py +4 -0
- django_cfg/management/commands/check_settings.py +6 -0
- django_cfg/management/commands/clear_constance.py +5 -2
- django_cfg/management/commands/create_token.py +6 -0
- django_cfg/management/commands/list_urls.py +6 -0
- django_cfg/management/commands/migrate_all.py +6 -0
- django_cfg/management/commands/migrator.py +3 -0
- django_cfg/management/commands/rundramatiq.py +6 -0
- django_cfg/management/commands/runserver_ngrok.py +51 -29
- django_cfg/management/commands/script.py +6 -0
- django_cfg/management/commands/show_config.py +12 -2
- django_cfg/management/commands/show_urls.py +4 -0
- django_cfg/management/commands/superuser.py +6 -0
- django_cfg/management/commands/task_clear.py +4 -1
- django_cfg/management/commands/task_status.py +3 -1
- django_cfg/management/commands/test_email.py +3 -0
- django_cfg/management/commands/test_telegram.py +6 -0
- django_cfg/management/commands/test_twilio.py +6 -0
- django_cfg/management/commands/tree.py +6 -0
- django_cfg/management/commands/validate_config.py +155 -149
- django_cfg/models/constance.py +31 -11
- django_cfg/models/payments.py +175 -498
- django_cfg/modules/django_currency/__init__.py +16 -11
- django_cfg/modules/django_currency/clients/__init__.py +4 -4
- django_cfg/modules/django_currency/clients/coinpaprika_client.py +289 -0
- django_cfg/modules/django_currency/clients/yahoo_client.py +157 -0
- django_cfg/modules/django_currency/core/__init__.py +1 -7
- django_cfg/modules/django_currency/core/converter.py +18 -23
- django_cfg/modules/django_currency/core/models.py +122 -11
- django_cfg/modules/django_currency/database/__init__.py +4 -4
- django_cfg/modules/django_currency/database/database_loader.py +190 -309
- django_cfg/modules/django_logger.py +160 -146
- django_cfg/modules/django_unfold/dashboard.py +65 -12
- django_cfg/registry/core.py +1 -0
- django_cfg/template_archive/django_sample.zip +0 -0
- django_cfg/templates/admin/components/action_grid.html +9 -9
- django_cfg/templates/admin/components/metric_card.html +5 -5
- django_cfg/templates/admin/components/status_badge.html +2 -2
- django_cfg/templates/admin/layouts/dashboard_with_tabs.html +152 -24
- django_cfg/templates/admin/snippets/components/quick_actions.html +3 -3
- django_cfg/templates/admin/snippets/components/system_health.html +1 -1
- django_cfg/templates/admin/snippets/tabs/overview_tab.html +49 -52
- django_cfg/utils/smart_defaults.py +222 -571
- django_cfg/utils/toolkit.py +51 -11
- {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/METADATA +5 -4
- {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/RECORD +172 -182
- django_cfg/apps/payments/__init__.py +0 -8
- django_cfg/apps/payments/admin/tariffs_admin.py +0 -199
- django_cfg/apps/payments/config/module.py +0 -70
- django_cfg/apps/payments/config/providers.py +0 -105
- django_cfg/apps/payments/config/settings.py +0 -96
- django_cfg/apps/payments/config/utils.py +0 -52
- django_cfg/apps/payments/decorators.py +0 -291
- django_cfg/apps/payments/management/commands/README.md +0 -178
- django_cfg/apps/payments/management/commands/currency_stats.py +0 -323
- django_cfg/apps/payments/management/commands/populate_currencies.py +0 -246
- django_cfg/apps/payments/management/commands/update_currencies.py +0 -336
- django_cfg/apps/payments/managers/__init__.py +0 -22
- django_cfg/apps/payments/managers/api_key_manager.py +0 -35
- django_cfg/apps/payments/managers/balance_manager.py +0 -361
- django_cfg/apps/payments/managers/currency_manager.py +0 -83
- django_cfg/apps/payments/managers/payment_manager.py +0 -44
- django_cfg/apps/payments/managers/subscription_manager.py +0 -37
- django_cfg/apps/payments/managers/tariff_manager.py +0 -29
- django_cfg/apps/payments/models/events.py +0 -73
- django_cfg/apps/payments/serializers/__init__.py +0 -56
- django_cfg/apps/payments/serializers/api_keys.py +0 -51
- django_cfg/apps/payments/serializers/balance.py +0 -59
- django_cfg/apps/payments/serializers/currencies.py +0 -55
- django_cfg/apps/payments/serializers/payments.py +0 -62
- django_cfg/apps/payments/serializers/subscriptions.py +0 -71
- django_cfg/apps/payments/serializers/tariffs.py +0 -56
- django_cfg/apps/payments/services/billing/__init__.py +0 -8
- django_cfg/apps/payments/services/cache/base.py +0 -30
- django_cfg/apps/payments/services/core/fallback_service.py +0 -432
- django_cfg/apps/payments/services/internal_types.py +0 -297
- django_cfg/apps/payments/services/middleware/__init__.py +0 -8
- django_cfg/apps/payments/services/monitoring/__init__.py +0 -22
- django_cfg/apps/payments/services/monitoring/api_schemas.py +0 -222
- django_cfg/apps/payments/services/monitoring/provider_health.py +0 -372
- django_cfg/apps/payments/services/providers/cryptapi.py +0 -273
- django_cfg/apps/payments/services/providers/cryptomus.py +0 -311
- django_cfg/apps/payments/services/security/__init__.py +0 -34
- django_cfg/apps/payments/services/security/error_handler.py +0 -637
- django_cfg/apps/payments/services/security/payment_notifications.py +0 -342
- django_cfg/apps/payments/services/security/webhook_validator.py +0 -475
- django_cfg/apps/payments/services/validators/__init__.py +0 -8
- django_cfg/apps/payments/static/payments/css/payments.css +0 -340
- django_cfg/apps/payments/static/payments/js/notifications.js +0 -202
- django_cfg/apps/payments/static/payments/js/payment-utils.js +0 -318
- django_cfg/apps/payments/static/payments/js/theme.js +0 -86
- django_cfg/apps/payments/tasks/__init__.py +0 -12
- django_cfg/apps/payments/tasks/webhook_processing.py +0 -177
- django_cfg/apps/payments/templates/payments/base.html +0 -182
- django_cfg/apps/payments/templates/payments/components/payment_card.html +0 -201
- django_cfg/apps/payments/templates/payments/components/payment_qr_code.html +0 -109
- django_cfg/apps/payments/templates/payments/components/progress_bar.html +0 -36
- django_cfg/apps/payments/templates/payments/components/provider_stats.html +0 -40
- django_cfg/apps/payments/templates/payments/components/status_badge.html +0 -27
- django_cfg/apps/payments/templates/payments/components/status_overview.html +0 -144
- django_cfg/apps/payments/templates/payments/dashboard.html +0 -346
- django_cfg/apps/payments/templatetags/payments_tags.py +0 -315
- django_cfg/apps/payments/urls_templates.py +0 -52
- django_cfg/apps/payments/utils/__init__.py +0 -45
- django_cfg/apps/payments/utils/billing_utils.py +0 -342
- django_cfg/apps/payments/utils/config_utils.py +0 -245
- django_cfg/apps/payments/utils/middleware_utils.py +0 -228
- django_cfg/apps/payments/utils/validation_utils.py +0 -94
- django_cfg/apps/payments/views/__init__.py +0 -62
- django_cfg/apps/payments/views/api_key_views.py +0 -164
- django_cfg/apps/payments/views/balance_views.py +0 -75
- django_cfg/apps/payments/views/currency_views.py +0 -111
- django_cfg/apps/payments/views/payment_views.py +0 -149
- django_cfg/apps/payments/views/subscription_views.py +0 -135
- django_cfg/apps/payments/views/tariff_views.py +0 -131
- django_cfg/apps/payments/views/templates/__init__.py +0 -25
- django_cfg/apps/payments/views/templates/ajax.py +0 -312
- django_cfg/apps/payments/views/templates/base.py +0 -204
- django_cfg/apps/payments/views/templates/dashboard.py +0 -60
- django_cfg/apps/payments/views/templates/payment_detail.py +0 -102
- django_cfg/apps/payments/views/templates/payment_management.py +0 -164
- django_cfg/apps/payments/views/templates/qr_code.py +0 -174
- django_cfg/apps/payments/views/templates/stats.py +0 -240
- django_cfg/apps/payments/views/templates/utils.py +0 -181
- django_cfg/apps/payments/views/webhook_views.py +0 -266
- django_cfg/apps/payments/viewsets.py +0 -65
- django_cfg/core/integration.py +0 -160
- django_cfg/modules/django_currency/clients/coingecko_client.py +0 -257
- django_cfg/modules/django_currency/clients/yfinance_client.py +0 -246
- django_cfg/template_archive/.gitignore +0 -1
- django_cfg/template_archive/__init__.py +0 -0
- django_cfg/urls.py +0 -33
- {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/WHEEL +0 -0
- {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/entry_points.txt +0 -0
- {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}")
|