django-cfg 1.2.31__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 -10
- 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 +526 -222
- django_cfg/apps/payments/admin/filters.py +306 -199
- django_cfg/apps/payments/admin/payments_admin.py +465 -70
- 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 +303 -151
- django_cfg/apps/payments/management/commands/manage_providers.py +333 -160
- 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 +342 -152
- django_cfg/apps/payments/middleware/usage_tracking.py +249 -240
- django_cfg/apps/payments/migrations/0001_initial.py +708 -536
- django_cfg/apps/payments/models/__init__.py +13 -18
- 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 +172 -148
- 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 -285
- 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 +346 -467
- django_cfg/apps/payments/services/core/subscription_service.py +425 -481
- 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 +234 -174
- django_cfg/apps/payments/services/providers/nowpayments.py +478 -0
- django_cfg/apps/payments/services/providers/registry.py +367 -301
- 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 +210 -129
- django_cfg/apps/payments/signals/balance_signals.py +174 -0
- django_cfg/apps/payments/signals/payment_signals.py +128 -103
- django_cfg/apps/payments/signals/subscription_signals.py +194 -142
- 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 +45 -48
- django_cfg/apps/payments/urls_admin.py +33 -42
- 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/config.py +1 -1
- django_cfg/core/config.py +40 -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 -492
- django_cfg/modules/django_logger.py +160 -146
- django_cfg/modules/django_unfold/dashboard.py +64 -16
- django_cfg/registry/core.py +1 -0
- django_cfg/template_archive/django_sample.zip +0 -0
- django_cfg/utils/smart_defaults.py +222 -571
- django_cfg/utils/toolkit.py +51 -11
- {django_cfg-1.2.31.dist-info ā django_cfg-1.3.1.dist-info}/METADATA +4 -1
- {django_cfg-1.2.31.dist-info ā django_cfg-1.3.1.dist-info}/RECORD +153 -185
- 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 -146
- django_cfg/apps/payments/management/commands/currency_stats.py +0 -304
- django_cfg/apps/payments/managers/__init__.py +0 -23
- 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 -306
- django_cfg/apps/payments/managers/payment_manager.py +0 -192
- django_cfg/apps/payments/managers/subscription_manager.py +0 -37
- django_cfg/apps/payments/managers/tariff_manager.py +0 -29
- django_cfg/apps/payments/migrations/0002_network_providercurrency_and_more.py +0 -241
- django_cfg/apps/payments/migrations/0003_add_usd_rate_cache.py +0 -30
- django_cfg/apps/payments/models/events.py +0 -73
- django_cfg/apps/payments/serializers/__init__.py +0 -57
- 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 -63
- 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 -461
- 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 -76
- django_cfg/apps/payments/services/monitoring/provider_health.py +0 -372
- django_cfg/apps/payments/services/providers/cryptapi/__init__.py +0 -4
- django_cfg/apps/payments/services/providers/cryptapi/config.py +0 -8
- django_cfg/apps/payments/services/providers/cryptapi/models.py +0 -192
- django_cfg/apps/payments/services/providers/cryptapi/provider.py +0 -439
- django_cfg/apps/payments/services/providers/cryptomus/__init__.py +0 -4
- django_cfg/apps/payments/services/providers/cryptomus/models.py +0 -176
- django_cfg/apps/payments/services/providers/cryptomus/provider.py +0 -429
- django_cfg/apps/payments/services/providers/cryptomus/provider_v2.py +0 -564
- django_cfg/apps/payments/services/providers/models/__init__.py +0 -34
- django_cfg/apps/payments/services/providers/models/currencies.py +0 -190
- django_cfg/apps/payments/services/providers/nowpayments/__init__.py +0 -4
- django_cfg/apps/payments/services/providers/nowpayments/models.py +0 -196
- django_cfg/apps/payments/services/providers/nowpayments/provider.py +0 -380
- django_cfg/apps/payments/services/providers/stripe/__init__.py +0 -4
- django_cfg/apps/payments/services/providers/stripe/models.py +0 -184
- django_cfg/apps/payments/services/providers/stripe/provider.py +0 -109
- django_cfg/apps/payments/services/security/__init__.py +0 -34
- django_cfg/apps/payments/services/security/error_handler.py +0 -635
- django_cfg/apps/payments/services/security/payment_notifications.py +0 -342
- django_cfg/apps/payments/services/security/webhook_validator.py +0 -474
- 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/admin/payments/currency/change_list.html +0 -50
- 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 -43
- django_cfg/apps/payments/templates/payments/components/provider_stats.html +0 -40
- django_cfg/apps/payments/templates/payments/components/status_badge.html +0 -34
- django_cfg/apps/payments/templates/payments/components/status_overview.html +0 -148
- django_cfg/apps/payments/templates/payments/dashboard.html +0 -258
- django_cfg/apps/payments/templates/payments/dashboard_simple_test.html +0 -35
- django_cfg/apps/payments/templates/payments/payment_create.html +0 -579
- django_cfg/apps/payments/templates/payments/payment_detail.html +0 -373
- django_cfg/apps/payments/templates/payments/payment_list.html +0 -354
- django_cfg/apps/payments/templates/payments/stats.html +0 -261
- django_cfg/apps/payments/templates/payments/test.html +0 -213
- django_cfg/apps/payments/templatetags/payments_tags.py +0 -315
- django_cfg/apps/payments/utils/__init__.py +0 -43
- django_cfg/apps/payments/utils/billing_utils.py +0 -342
- django_cfg/apps/payments/utils/config_utils.py +0 -239
- 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 -63
- 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 -122
- 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 -451
- django_cfg/apps/payments/views/templates/base.py +0 -212
- 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 -158
- django_cfg/apps/payments/views/templates/qr_code.py +0 -174
- django_cfg/apps/payments/views/templates/stats.py +0 -244
- 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 -66
- django_cfg/core/integration.py +0 -160
- 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.31.dist-info ā django_cfg-1.3.1.dist-info}/WHEEL +0 -0
- {django_cfg-1.2.31.dist-info ā django_cfg-1.3.1.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.2.31.dist-info ā django_cfg-1.3.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,235 +1,408 @@
|
|
1
1
|
"""
|
2
|
-
Universal
|
2
|
+
Provider management command for Universal Payment System v2.0.
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
Usage:
|
7
|
-
python manage.py manage_providers # Sync all active providers
|
8
|
-
python manage.py manage_providers --provider nowpayments # Sync specific provider
|
9
|
-
python manage.py manage_providers --with-rates # Sync providers + update USD rates
|
10
|
-
python manage.py manage_providers --stats # Show provider statistics
|
4
|
+
Manages payment provider synchronization, health checks, and statistics.
|
11
5
|
"""
|
12
6
|
|
13
7
|
from django.core.management.base import BaseCommand, CommandError
|
14
8
|
from django.db import transaction
|
15
9
|
from django.utils import timezone
|
16
|
-
from
|
10
|
+
from datetime import timedelta
|
11
|
+
from typing import List, Optional, Dict
|
17
12
|
import time
|
18
13
|
|
19
14
|
from django_cfg.modules.django_logger import get_logger
|
20
|
-
from django_cfg.apps.payments.
|
21
|
-
from django_cfg.apps.payments.
|
15
|
+
from django_cfg.apps.payments.models import Currency, Network, ProviderCurrency
|
16
|
+
from django_cfg.apps.payments.services.providers import get_provider_registry
|
22
17
|
|
23
18
|
logger = get_logger("manage_providers")
|
24
19
|
|
25
20
|
|
26
21
|
class Command(BaseCommand):
|
27
|
-
"""
|
22
|
+
"""
|
23
|
+
Universal provider management command.
|
24
|
+
|
25
|
+
Features:
|
26
|
+
- Provider currency synchronization
|
27
|
+
- Health monitoring and statistics
|
28
|
+
- Selective provider updates
|
29
|
+
- Integration with ProviderRegistry
|
30
|
+
"""
|
28
31
|
|
29
|
-
help = 'Manage payment providers
|
32
|
+
help = 'Manage payment providers and their currencies'
|
30
33
|
|
31
34
|
def add_arguments(self, parser):
|
32
|
-
"""Add command
|
35
|
+
"""Add command arguments."""
|
36
|
+
|
37
|
+
# Main operation modes
|
38
|
+
parser.add_argument(
|
39
|
+
'--all',
|
40
|
+
action='store_true',
|
41
|
+
help='Sync all active providers'
|
42
|
+
)
|
43
|
+
|
33
44
|
parser.add_argument(
|
34
45
|
'--provider',
|
35
46
|
type=str,
|
36
|
-
help='
|
47
|
+
help='Sync specific provider (e.g., nowpayments, cryptomus)'
|
48
|
+
)
|
49
|
+
|
50
|
+
parser.add_argument(
|
51
|
+
'--health-check',
|
52
|
+
action='store_true',
|
53
|
+
help='Perform health check on all providers'
|
37
54
|
)
|
55
|
+
|
38
56
|
parser.add_argument(
|
39
|
-
'--
|
57
|
+
'--stats',
|
40
58
|
action='store_true',
|
41
|
-
help='
|
59
|
+
help='Show provider statistics'
|
42
60
|
)
|
61
|
+
|
62
|
+
# Sync options
|
43
63
|
parser.add_argument(
|
44
64
|
'--with-rates',
|
45
65
|
action='store_true',
|
46
|
-
help='
|
66
|
+
help='Update USD rates after syncing currencies'
|
47
67
|
)
|
68
|
+
|
48
69
|
parser.add_argument(
|
49
|
-
'--
|
70
|
+
'--currencies',
|
71
|
+
type=str,
|
72
|
+
help='Comma-separated list of currency codes to sync (e.g., BTC,ETH,USDT)'
|
73
|
+
)
|
74
|
+
|
75
|
+
parser.add_argument(
|
76
|
+
'--force-refresh',
|
50
77
|
action='store_true',
|
51
|
-
help='
|
78
|
+
help='Force refresh even if recently synced'
|
52
79
|
)
|
80
|
+
|
81
|
+
# Behavior options
|
53
82
|
parser.add_argument(
|
54
83
|
'--dry-run',
|
55
84
|
action='store_true',
|
56
|
-
help='Show what would be
|
85
|
+
help='Show what would be done without making changes'
|
57
86
|
)
|
87
|
+
|
58
88
|
parser.add_argument(
|
59
89
|
'--verbose',
|
60
90
|
action='store_true',
|
61
|
-
help='Show detailed
|
91
|
+
help='Show detailed output'
|
62
92
|
)
|
63
|
-
|
93
|
+
|
64
94
|
def handle(self, *args, **options):
|
65
|
-
"""
|
66
|
-
start_time = time.time()
|
95
|
+
"""Main command handler."""
|
67
96
|
|
68
|
-
|
69
|
-
self.stdout.write(self.style.SUCCESS('š Provider Management Tool'))
|
70
|
-
self.stdout.write('=' * 60)
|
97
|
+
start_time = time.time()
|
71
98
|
|
72
|
-
|
73
|
-
|
99
|
+
try:
|
100
|
+
self.stdout.write(
|
101
|
+
self.style.SUCCESS('š Starting Provider Management')
|
102
|
+
)
|
74
103
|
|
75
|
-
|
76
|
-
|
77
|
-
provider_names = [p.strip() for p in options['provider'].split(',')]
|
78
|
-
elif options['all']:
|
79
|
-
provider_names = get_available_providers()
|
80
|
-
else:
|
81
|
-
# Default: sync active providers only
|
82
|
-
provider_names = get_available_providers()
|
104
|
+
# Get provider registry
|
105
|
+
registry = get_provider_registry()
|
83
106
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
107
|
+
# Determine operation mode
|
108
|
+
if options['health_check']:
|
109
|
+
self._perform_health_check(registry, options)
|
110
|
+
elif options['stats']:
|
111
|
+
self._show_provider_stats(registry, options)
|
112
|
+
elif options['provider']:
|
113
|
+
self._sync_single_provider(registry, options['provider'], options)
|
114
|
+
elif options['all']:
|
115
|
+
self._sync_all_providers(registry, options)
|
116
|
+
else:
|
117
|
+
# Default: show available providers and basic stats
|
118
|
+
self._show_available_providers(registry, options)
|
89
119
|
|
90
|
-
|
91
|
-
|
92
|
-
self.stdout.write(
|
93
|
-
|
120
|
+
# Show summary
|
121
|
+
elapsed = time.time() - start_time
|
122
|
+
self.stdout.write(
|
123
|
+
self.style.SUCCESS(
|
124
|
+
f'ā
Provider management completed in {elapsed:.1f}s'
|
125
|
+
)
|
126
|
+
)
|
94
127
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
128
|
+
except Exception as e:
|
129
|
+
self.stdout.write(
|
130
|
+
self.style.ERROR(f'ā Provider management failed: {e}')
|
131
|
+
)
|
132
|
+
logger.error(f"Provider management command failed: {e}")
|
133
|
+
raise CommandError(f"Command failed: {e}")
|
134
|
+
|
135
|
+
def _sync_all_providers(self, registry, options):
|
136
|
+
"""Sync all active providers."""
|
137
|
+
|
138
|
+
self.stdout.write("š Syncing all active providers...")
|
139
|
+
|
140
|
+
available_providers = registry.get_available_providers()
|
141
|
+
|
142
|
+
if not available_providers:
|
143
|
+
self.stdout.write("ā ļø No active providers found")
|
144
|
+
return
|
145
|
+
|
146
|
+
total_synced = 0
|
147
|
+
total_errors = 0
|
148
|
+
|
149
|
+
for provider_name in available_providers:
|
150
|
+
try:
|
151
|
+
synced_count = self._sync_provider(registry, provider_name, options)
|
152
|
+
total_synced += synced_count
|
153
|
+
|
154
|
+
except Exception as e:
|
155
|
+
self.stdout.write(f"ā Failed to sync {provider_name}: {e}")
|
156
|
+
total_errors += 1
|
157
|
+
logger.error(f"Provider sync failed for {provider_name}: {e}")
|
101
158
|
|
102
|
-
|
103
|
-
|
159
|
+
self.stdout.write(
|
160
|
+
f"š All providers sync complete: {total_synced} currencies synced, {total_errors} errors"
|
161
|
+
)
|
104
162
|
|
105
|
-
|
163
|
+
# Update rates if requested
|
164
|
+
if options['with_rates'] and not options['dry_run']:
|
165
|
+
self._update_rates_after_sync(options)
|
166
|
+
|
167
|
+
def _sync_single_provider(self, registry, provider_name: str, options):
|
106
168
|
"""Sync a specific provider."""
|
107
|
-
|
169
|
+
|
170
|
+
self.stdout.write(f"š Syncing provider: {provider_name}")
|
108
171
|
|
109
172
|
try:
|
110
|
-
|
173
|
+
synced_count = self._sync_provider(registry, provider_name, options)
|
174
|
+
self.stdout.write(f"ā
{provider_name} sync complete: {synced_count} currencies")
|
111
175
|
|
112
|
-
if
|
113
|
-
|
114
|
-
self.
|
115
|
-
|
176
|
+
# Update rates if requested
|
177
|
+
if options['with_rates'] and not options['dry_run']:
|
178
|
+
self._update_rates_after_sync(options)
|
179
|
+
|
180
|
+
except Exception as e:
|
181
|
+
self.stdout.write(f"ā Failed to sync {provider_name}: {e}")
|
182
|
+
logger.error(f"Provider sync failed for {provider_name}: {e}")
|
183
|
+
raise CommandError(f"Provider sync failed: {e}")
|
184
|
+
|
185
|
+
def _sync_provider(self, registry, provider_name: str, options) -> int:
|
186
|
+
"""Sync currencies for a specific provider."""
|
187
|
+
|
188
|
+
try:
|
189
|
+
provider = registry.get_provider(provider_name)
|
190
|
+
if not provider:
|
191
|
+
raise CommandError(f"Provider '{provider_name}' not available")
|
192
|
+
|
193
|
+
self.stdout.write(f" š” Fetching currencies from {provider_name}...")
|
116
194
|
|
117
195
|
if options['dry_run']:
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
self.stdout.write(f" š° Would sync {currency_count} currencies")
|
131
|
-
self.stdout.write(f" š Would sync {network_count} networks")
|
132
|
-
|
133
|
-
return currency_count + network_count
|
134
|
-
|
135
|
-
except Exception as e:
|
136
|
-
self.stdout.write(f" ā Failed to fetch currencies: {e}")
|
137
|
-
return 0
|
196
|
+
self.stdout.write(f" [DRY RUN] Would sync {provider_name} currencies")
|
197
|
+
return 0
|
198
|
+
|
199
|
+
# Use provider's sync method
|
200
|
+
sync_result = provider.sync_currencies_to_db()
|
201
|
+
|
202
|
+
if sync_result.errors:
|
203
|
+
for error in sync_result.errors:
|
204
|
+
self.stdout.write(f" ā ļø {error}")
|
205
|
+
|
206
|
+
synced_count = sync_result.currencies_created + sync_result.currencies_updated
|
138
207
|
|
139
|
-
else:
|
140
|
-
# Live sync
|
141
|
-
with transaction.atomic():
|
142
|
-
sync_result = provider.sync_currencies_to_db()
|
143
|
-
|
144
|
-
if options['verbose']:
|
145
|
-
self.stdout.write(f" ā
Synced {sync_result.total_items_processed} items")
|
146
|
-
if sync_result.errors:
|
147
|
-
self.stdout.write(f" ā ļø Errors: {len(sync_result.errors)}")
|
148
|
-
for error in sync_result.errors[:3]: # Show first 3 errors
|
149
|
-
self.stdout.write(f" ⢠{error}")
|
150
|
-
|
151
|
-
self.stdout.write(
|
152
|
-
self.style.SUCCESS(f"ā
{provider_name}: {sync_result.total_items_processed} items synced")
|
153
|
-
)
|
154
|
-
|
155
|
-
return sync_result.total_items_processed
|
156
|
-
|
157
|
-
except Exception as e:
|
158
|
-
logger.exception(f"Error syncing provider {provider_name}")
|
159
208
|
self.stdout.write(
|
160
|
-
|
209
|
+
f" ā
{provider_name}: {sync_result.currencies_created} created, "
|
210
|
+
f"{sync_result.currencies_updated} updated, "
|
211
|
+
f"{sync_result.provider_currencies_created} provider mappings created"
|
161
212
|
)
|
162
|
-
return 0
|
163
213
|
|
164
|
-
|
165
|
-
|
166
|
-
try:
|
167
|
-
# Get currencies that need rate updates
|
168
|
-
from datetime import timedelta
|
169
|
-
from django.db.models import Q
|
170
|
-
|
171
|
-
stale_threshold = timezone.now() - timedelta(hours=12)
|
172
|
-
currencies_to_update = Currency.objects.filter(
|
173
|
-
Q(usd_rate__isnull=True) |
|
174
|
-
Q(rate_updated_at__isnull=True) |
|
175
|
-
Q(rate_updated_at__lt=stale_threshold)
|
176
|
-
)[:50] # Limit to avoid long execution
|
177
|
-
|
178
|
-
updated_count = 0
|
179
|
-
for currency in currencies_to_update:
|
180
|
-
try:
|
181
|
-
rate = Currency.objects.get_usd_rate(currency.code, force_refresh=True)
|
182
|
-
if rate > 0:
|
183
|
-
updated_count += 1
|
184
|
-
self.stdout.write(f" ā
{currency.code}: ${rate:.8f}")
|
185
|
-
except Exception as e:
|
186
|
-
self.stdout.write(f" ā ļø {currency.code}: {str(e)}")
|
187
|
-
|
188
|
-
self.stdout.write(f"š± Updated {updated_count} exchange rates")
|
214
|
+
if options['verbose']:
|
215
|
+
self._show_provider_sync_details(sync_result)
|
189
216
|
|
190
|
-
|
191
|
-
self.stdout.write(f"ā ļø Rate update failed: {e}")
|
217
|
+
return synced_count
|
192
218
|
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
219
|
+
except Exception as e:
|
220
|
+
logger.error(f"Provider sync failed for {provider_name}: {e}")
|
221
|
+
raise
|
222
|
+
|
223
|
+
def _perform_health_check(self, registry, options):
|
224
|
+
"""Perform health check on all providers."""
|
225
|
+
|
226
|
+
self.stdout.write("š„ Performing provider health check...")
|
227
|
+
|
228
|
+
available_providers = registry.get_available_providers()
|
229
|
+
|
230
|
+
if not available_providers:
|
231
|
+
self.stdout.write("ā ļø No providers configured")
|
232
|
+
return
|
233
|
+
|
234
|
+
healthy_count = 0
|
235
|
+
unhealthy_count = 0
|
197
236
|
|
198
|
-
# Available providers
|
199
|
-
available_providers = get_available_providers()
|
200
|
-
self.stdout.write(f"š¢ Available providers: {len(available_providers)}")
|
201
237
|
for provider_name in available_providers:
|
202
238
|
try:
|
203
|
-
provider =
|
204
|
-
|
205
|
-
|
206
|
-
|
239
|
+
provider = registry.get_provider(provider_name)
|
240
|
+
|
241
|
+
if not provider:
|
242
|
+
self.stdout.write(f" ā {provider_name}: Not available")
|
243
|
+
unhealthy_count += 1
|
244
|
+
continue
|
245
|
+
|
246
|
+
# Check if provider is enabled
|
247
|
+
if not provider.is_enabled():
|
248
|
+
self.stdout.write(f" āøļø {provider_name}: Disabled")
|
249
|
+
continue
|
250
|
+
|
251
|
+
# Perform basic health check (try to get currencies)
|
252
|
+
start_time = time.time()
|
253
|
+
|
254
|
+
try:
|
255
|
+
currencies = provider.get_parsed_currencies()
|
256
|
+
response_time = time.time() - start_time
|
257
|
+
|
258
|
+
if currencies and len(currencies.currencies) > 0:
|
259
|
+
self.stdout.write(
|
260
|
+
f" ā
{provider_name}: Healthy "
|
261
|
+
f"({len(currencies.currencies)} currencies, {response_time:.2f}s)"
|
262
|
+
)
|
263
|
+
healthy_count += 1
|
264
|
+
else:
|
265
|
+
self.stdout.write(f" ā ļø {provider_name}: No currencies returned")
|
266
|
+
unhealthy_count += 1
|
267
|
+
|
268
|
+
except Exception as e:
|
269
|
+
response_time = time.time() - start_time
|
270
|
+
self.stdout.write(
|
271
|
+
f" ā {provider_name}: Error ({response_time:.2f}s) - {str(e)[:50]}"
|
272
|
+
)
|
273
|
+
unhealthy_count += 1
|
274
|
+
|
207
275
|
except Exception as e:
|
208
|
-
self.stdout.write(f"
|
276
|
+
self.stdout.write(f" ā {provider_name}: Critical error - {e}")
|
277
|
+
unhealthy_count += 1
|
278
|
+
|
279
|
+
self.stdout.write(
|
280
|
+
f"š„ Health check complete: {healthy_count} healthy, {unhealthy_count} unhealthy"
|
281
|
+
)
|
282
|
+
|
283
|
+
def _show_provider_stats(self, registry, options):
|
284
|
+
"""Show detailed provider statistics."""
|
285
|
+
|
286
|
+
self.stdout.write("š Provider Statistics:")
|
209
287
|
|
210
|
-
|
288
|
+
# Registry stats
|
289
|
+
available_providers = registry.get_available_providers()
|
290
|
+
self.stdout.write(f"\nš§ Registry Status:")
|
291
|
+
self.stdout.write(f" Available providers: {len(available_providers)}")
|
292
|
+
|
293
|
+
for provider_name in available_providers:
|
294
|
+
provider = registry.get_provider(provider_name)
|
295
|
+
status = "ā
Enabled" if provider and provider.is_enabled() else "ā Disabled"
|
296
|
+
self.stdout.write(f" - {provider_name}: {status}")
|
297
|
+
|
298
|
+
# Database stats
|
299
|
+
self.stdout.write(f"\nš¾ Database Statistics:")
|
211
300
|
|
212
|
-
# Database statistics
|
213
|
-
total_currencies = Currency.objects.count()
|
214
301
|
total_provider_currencies = ProviderCurrency.objects.count()
|
302
|
+
enabled_provider_currencies = ProviderCurrency.objects.filter(is_enabled=True).count()
|
215
303
|
|
216
|
-
self.stdout.write(f"
|
217
|
-
self.stdout.write(f"
|
304
|
+
self.stdout.write(f" Total provider currencies: {total_provider_currencies}")
|
305
|
+
self.stdout.write(f" Enabled: {enabled_provider_currencies}")
|
218
306
|
|
219
|
-
#
|
307
|
+
# Stats by provider
|
220
308
|
from django.db.models import Count
|
309
|
+
|
221
310
|
provider_stats = ProviderCurrency.objects.values('provider_name').annotate(
|
222
|
-
|
223
|
-
|
311
|
+
total=Count('id'),
|
312
|
+
enabled=Count('id', filter=models.Q(is_enabled=True))
|
313
|
+
).order_by('-total')
|
224
314
|
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
315
|
+
if provider_stats:
|
316
|
+
self.stdout.write(f"\nš By Provider:")
|
317
|
+
for stat in provider_stats:
|
318
|
+
self.stdout.write(
|
319
|
+
f" - {stat['provider_name']}: {stat['total']} total, {stat['enabled']} enabled"
|
320
|
+
)
|
321
|
+
|
322
|
+
# Recent activity
|
323
|
+
recent_threshold = timezone.now() - timedelta(hours=24)
|
324
|
+
recent_updates = ProviderCurrency.objects.filter(
|
325
|
+
updated_at__gte=recent_threshold
|
326
|
+
).count()
|
327
|
+
|
328
|
+
self.stdout.write(f"\nš Recent Activity (24h):")
|
329
|
+
self.stdout.write(f" Updated currencies: {recent_updates}")
|
330
|
+
|
331
|
+
# Rate coverage
|
332
|
+
currencies_with_rates = ProviderCurrency.objects.filter(
|
333
|
+
usd_rate__isnull=False
|
334
|
+
).count()
|
335
|
+
|
336
|
+
rate_coverage = (currencies_with_rates / total_provider_currencies * 100) if total_provider_currencies > 0 else 0
|
337
|
+
|
338
|
+
self.stdout.write(f"\nš± Rate Coverage:")
|
339
|
+
self.stdout.write(f" Currencies with USD rates: {currencies_with_rates} ({rate_coverage:.1f}%)")
|
340
|
+
|
341
|
+
def _show_available_providers(self, registry, options):
|
342
|
+
"""Show available providers and basic info."""
|
343
|
+
|
344
|
+
self.stdout.write("š Available Providers:")
|
345
|
+
|
346
|
+
available_providers = registry.get_available_providers()
|
347
|
+
|
348
|
+
if not available_providers:
|
349
|
+
self.stdout.write(" No providers configured")
|
350
|
+
return
|
351
|
+
|
352
|
+
for provider_name in available_providers:
|
353
|
+
try:
|
354
|
+
provider = registry.get_provider(provider_name)
|
355
|
+
|
356
|
+
if provider:
|
357
|
+
status = "ā
Enabled" if provider.is_enabled() else "ā Disabled"
|
358
|
+
|
359
|
+
# Get currency count from database
|
360
|
+
currency_count = ProviderCurrency.objects.filter(
|
361
|
+
provider_name=provider_name
|
362
|
+
).count()
|
363
|
+
|
364
|
+
self.stdout.write(
|
365
|
+
f" - {provider_name}: {status} ({currency_count} currencies)"
|
366
|
+
)
|
367
|
+
else:
|
368
|
+
self.stdout.write(f" - {provider_name}: ā Not available")
|
369
|
+
|
370
|
+
except Exception as e:
|
371
|
+
self.stdout.write(f" - {provider_name}: ā Error - {e}")
|
372
|
+
|
373
|
+
self.stdout.write(f"\nUse --all to sync all providers or --provider <name> for specific provider")
|
374
|
+
|
375
|
+
def _show_provider_sync_details(self, sync_result):
|
376
|
+
"""Show detailed sync results."""
|
232
377
|
|
233
|
-
self.stdout.write(
|
378
|
+
self.stdout.write(" š Sync Details:")
|
379
|
+
self.stdout.write(f" Currencies created: {sync_result.currencies_created}")
|
380
|
+
self.stdout.write(f" Currencies updated: {sync_result.currencies_updated}")
|
381
|
+
self.stdout.write(f" Networks created: {sync_result.networks_created}")
|
382
|
+
self.stdout.write(f" Provider currencies created: {sync_result.provider_currencies_created}")
|
383
|
+
self.stdout.write(f" Provider currencies updated: {sync_result.provider_currencies_updated}")
|
234
384
|
|
235
|
-
|
385
|
+
if sync_result.errors:
|
386
|
+
self.stdout.write(f" Errors: {len(sync_result.errors)}")
|
387
|
+
|
388
|
+
def _update_rates_after_sync(self, options):
|
389
|
+
"""Update USD rates after provider sync."""
|
390
|
+
|
391
|
+
self.stdout.write("š± Updating USD rates after sync...")
|
392
|
+
|
393
|
+
try:
|
394
|
+
from django.core.management import call_command
|
395
|
+
|
396
|
+
# Update rates for currencies that were just synced
|
397
|
+
if options['currencies']:
|
398
|
+
currency_codes = options['currencies'].split(',')
|
399
|
+
for code in currency_codes:
|
400
|
+
call_command('manage_currencies', '--currency', code.strip(), '--rates-only')
|
401
|
+
else:
|
402
|
+
call_command('manage_currencies', '--rates-only')
|
403
|
+
|
404
|
+
self.stdout.write("š± Rate update completed")
|
405
|
+
|
406
|
+
except Exception as e:
|
407
|
+
self.stdout.write(f"ā ļø Rate update failed: {e}")
|
408
|
+
logger.warning(f"Rate update after sync failed: {e}")
|