django-cfg 1.2.31__py3-none-any.whl → 1.3.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/cleanup_expired_data.py +419 -0
- django_cfg/apps/payments/management/commands/currency_stats.py +297 -225
- 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/management/commands/process_pending_payments.py +357 -0
- django_cfg/apps/payments/management/commands/test_providers.py +434 -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 +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 +153 -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_service/__init__.py +143 -0
- django_cfg/apps/payments/services/cache_service/api_key_cache.py +37 -0
- django_cfg/apps/payments/services/{cache/base.py → cache_service/interfaces.py} +3 -1
- django_cfg/apps/payments/services/cache_service/keys.py +49 -0
- django_cfg/apps/payments/services/cache_service/rate_limit_cache.py +47 -0
- django_cfg/apps/payments/services/cache_service/simple_cache.py +101 -0
- 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 +371 -465
- 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 +227 -570
- django_cfg/utils/toolkit.py +51 -11
- {django_cfg-1.2.31.dist-info → django_cfg-1.3.3.dist-info}/METADATA +4 -1
- {django_cfg-1.2.31.dist-info → django_cfg-1.3.3.dist-info}/RECORD +162 -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/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/simple_cache.py +0 -135
- 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.3.dist-info}/WHEEL +0 -0
- {django_cfg-1.2.31.dist-info → django_cfg-1.3.3.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.2.31.dist-info → django_cfg-1.3.3.dist-info}/licenses/LICENSE +0 -0
@@ -1,43 +1,52 @@
|
|
1
1
|
"""
|
2
|
-
Management
|
2
|
+
Currency Statistics Management Command for Universal Payment System v2.0.
|
3
3
|
|
4
|
-
|
5
|
-
python manage.py currency_stats
|
6
|
-
python manage.py currency_stats --detailed
|
7
|
-
python manage.py currency_stats --top 10
|
8
|
-
python manage.py currency_stats --check-rates
|
4
|
+
Display comprehensive currency database statistics and health information.
|
9
5
|
"""
|
10
6
|
|
11
7
|
from datetime import datetime, timedelta
|
12
|
-
from typing import List
|
8
|
+
from typing import List, Dict, Any
|
9
|
+
from decimal import Decimal
|
13
10
|
|
14
|
-
from django.core.management.base import BaseCommand
|
11
|
+
from django.core.management.base import BaseCommand, CommandError
|
15
12
|
from django.utils import timezone
|
16
|
-
from django.db.models import Q, Count, Avg
|
13
|
+
from django.db.models import Q, Count, Avg, Sum, Max, Min
|
14
|
+
from django.contrib.humanize.templatetags.humanize import intcomma
|
17
15
|
|
18
|
-
from django_cfg.
|
16
|
+
from django_cfg.modules.django_logger import get_logger
|
17
|
+
from django_cfg.apps.payments.models import Currency, Network, ProviderCurrency, UniversalPayment
|
18
|
+
from django_cfg.apps.payments.services.providers.registry import get_provider_registry
|
19
|
+
|
20
|
+
logger = get_logger("currency_stats")
|
19
21
|
|
20
22
|
|
21
23
|
class Command(BaseCommand):
|
22
24
|
"""
|
23
25
|
Display currency database statistics and health information.
|
26
|
+
|
27
|
+
Features:
|
28
|
+
- Basic and detailed statistics
|
29
|
+
- Top currencies by usage and value
|
30
|
+
- Rate freshness analysis
|
31
|
+
- Provider currency coverage
|
32
|
+
- Payment volume analysis
|
24
33
|
"""
|
25
34
|
|
26
|
-
help = 'Show currency database statistics'
|
35
|
+
help = 'Show currency database statistics and health information'
|
27
36
|
|
28
37
|
def add_arguments(self, parser):
|
29
38
|
"""Add command line arguments."""
|
30
39
|
parser.add_argument(
|
31
40
|
'--detailed',
|
32
41
|
action='store_true',
|
33
|
-
help='Show detailed statistics'
|
42
|
+
help='Show detailed statistics breakdown'
|
34
43
|
)
|
35
44
|
|
36
45
|
parser.add_argument(
|
37
46
|
'--top',
|
38
47
|
type=int,
|
39
|
-
default=
|
40
|
-
help='Number of top currencies to show (default:
|
48
|
+
default=10,
|
49
|
+
help='Number of top currencies to show (default: 10)'
|
41
50
|
)
|
42
51
|
|
43
52
|
parser.add_argument(
|
@@ -46,259 +55,322 @@ class Command(BaseCommand):
|
|
46
55
|
help='Check for outdated exchange rates'
|
47
56
|
)
|
48
57
|
|
58
|
+
parser.add_argument(
|
59
|
+
'--provider',
|
60
|
+
type=str,
|
61
|
+
help='Show statistics for specific provider'
|
62
|
+
)
|
63
|
+
|
49
64
|
parser.add_argument(
|
50
65
|
'--export-csv',
|
51
66
|
type=str,
|
52
|
-
help='Export
|
67
|
+
help='Export statistics to CSV file'
|
68
|
+
)
|
69
|
+
|
70
|
+
parser.add_argument(
|
71
|
+
'--format',
|
72
|
+
choices=['table', 'json', 'yaml'],
|
73
|
+
default='table',
|
74
|
+
help='Output format (default: table)'
|
53
75
|
)
|
54
76
|
|
55
77
|
def handle(self, *args, **options):
|
56
|
-
"""
|
57
|
-
|
78
|
+
"""Execute the command."""
|
79
|
+
try:
|
80
|
+
self.options = options
|
81
|
+
self.show_header()
|
82
|
+
|
83
|
+
if options['check_rates']:
|
84
|
+
self.check_rate_freshness()
|
85
|
+
elif options['provider']:
|
86
|
+
self.show_provider_stats(options['provider'])
|
87
|
+
else:
|
88
|
+
self.show_general_stats()
|
89
|
+
|
90
|
+
if options['detailed']:
|
91
|
+
self.show_detailed_stats()
|
92
|
+
|
93
|
+
self.show_top_currencies()
|
94
|
+
|
95
|
+
if options['export_csv']:
|
96
|
+
self.export_to_csv(options['export_csv'])
|
97
|
+
|
98
|
+
except Exception as e:
|
99
|
+
logger.error(f"Currency stats command failed: {e}")
|
100
|
+
raise CommandError(f"Failed to generate currency statistics: {e}")
|
101
|
+
|
102
|
+
def show_header(self):
|
103
|
+
"""Display command header."""
|
58
104
|
self.stdout.write(
|
59
|
-
self.style.SUCCESS(
|
105
|
+
self.style.SUCCESS("=" * 60)
|
60
106
|
)
|
61
|
-
self.stdout.write(
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
self._check_rate_freshness()
|
70
|
-
|
71
|
-
if options['export_csv']:
|
72
|
-
self._export_to_csv(options['export_csv'])
|
107
|
+
self.stdout.write(
|
108
|
+
self.style.SUCCESS("📊 CURRENCY DATABASE STATISTICS")
|
109
|
+
)
|
110
|
+
self.stdout.write(
|
111
|
+
self.style.SUCCESS("=" * 60)
|
112
|
+
)
|
113
|
+
self.stdout.write(f"Generated: {timezone.now().strftime('%Y-%m-%d %H:%M:%S UTC')}")
|
114
|
+
self.stdout.write("")
|
73
115
|
|
74
|
-
def
|
75
|
-
"""Show
|
76
|
-
|
116
|
+
def show_general_stats(self):
|
117
|
+
"""Show general currency statistics."""
|
77
118
|
# Basic counts
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
self.stdout.write(
|
96
|
-
self.stdout.write(
|
97
|
-
|
98
|
-
|
99
|
-
|
119
|
+
total_currencies = Currency.objects.count()
|
120
|
+
active_currencies = Currency.objects.filter(is_active=True).count()
|
121
|
+
crypto_currencies = Currency.objects.filter(currency_type='crypto').count()
|
122
|
+
fiat_currencies = Currency.objects.filter(currency_type='fiat').count()
|
123
|
+
|
124
|
+
# Network stats
|
125
|
+
total_networks = Network.objects.count()
|
126
|
+
active_networks = Network.objects.filter(is_active=True).count()
|
127
|
+
|
128
|
+
# Provider currency stats
|
129
|
+
total_provider_currencies = ProviderCurrency.objects.count()
|
130
|
+
active_provider_currencies = ProviderCurrency.objects.filter(is_active=True).count()
|
131
|
+
|
132
|
+
# Payment stats
|
133
|
+
total_payments = UniversalPayment.objects.count()
|
134
|
+
completed_payments = UniversalPayment.objects.filter(status='completed').count()
|
135
|
+
|
136
|
+
self.stdout.write(self.style.SUCCESS("📈 GENERAL STATISTICS"))
|
137
|
+
self.stdout.write("-" * 40)
|
138
|
+
|
139
|
+
stats = [
|
140
|
+
("Total Currencies", total_currencies),
|
141
|
+
("Active Currencies", active_currencies),
|
142
|
+
("Cryptocurrency", crypto_currencies),
|
143
|
+
("Fiat Currency", fiat_currencies),
|
144
|
+
("Networks", f"{active_networks}/{total_networks}"),
|
145
|
+
("Provider Currencies", f"{active_provider_currencies}/{total_provider_currencies}"),
|
146
|
+
("Total Payments", total_payments),
|
147
|
+
("Completed Payments", completed_payments),
|
148
|
+
]
|
100
149
|
|
101
|
-
|
102
|
-
|
103
|
-
recent_updates = Currency.objects.filter(
|
104
|
-
rate_updated_at__gte=recent_threshold
|
105
|
-
).count()
|
150
|
+
for label, value in stats:
|
151
|
+
self.stdout.write(f"{label:<20}: {self.style.WARNING(str(value))}")
|
106
152
|
|
107
|
-
|
108
|
-
outdated_threshold = now - timedelta(days=7)
|
109
|
-
outdated = Currency.objects.filter(
|
110
|
-
Q(rate_updated_at__lt=outdated_threshold) | Q(rate_updated_at__isnull=True)
|
111
|
-
).count()
|
112
|
-
|
113
|
-
self.stdout.write(f"\n🕒 Rate Updates:")
|
114
|
-
self.stdout.write(f" Updated in last 24h: {recent_updates}")
|
115
|
-
self.stdout.write(f" Outdated (>7 days): {outdated}")
|
116
|
-
|
117
|
-
# Top cryptocurrencies by USD value
|
118
|
-
top_crypto = Currency.objects.filter(
|
119
|
-
currency_type=Currency.CurrencyType.CRYPTO,
|
120
|
-
).order_by('-usd_rate')[:options['top']]
|
121
|
-
|
122
|
-
if top_crypto:
|
123
|
-
self.stdout.write(f"\n🚀 Top {options['top']} Cryptocurrencies by USD Rate:")
|
124
|
-
for i, currency in enumerate(top_crypto, 1):
|
125
|
-
age = self._get_rate_age(currency)
|
126
|
-
self.stdout.write(
|
127
|
-
f" {i}. {currency.code}: ${currency.usd_rate:,.6f} {age}"
|
128
|
-
)
|
129
|
-
|
130
|
-
# Major fiat currencies
|
131
|
-
major_fiat = Currency.objects.filter(
|
132
|
-
currency_type=Currency.CurrencyType.FIAT,
|
133
|
-
code__in=['USD', 'EUR', 'GBP', 'JPY', 'CNY'],
|
134
|
-
).order_by('code')
|
135
|
-
|
136
|
-
if major_fiat:
|
137
|
-
self.stdout.write(f"\n💵 Major Fiat Currencies:")
|
138
|
-
for currency in major_fiat:
|
139
|
-
age = self._get_rate_age(currency)
|
140
|
-
self.stdout.write(
|
141
|
-
f" • {currency.code}: {currency.name} = ${currency.usd_rate:.6f} {age}"
|
142
|
-
)
|
153
|
+
self.stdout.write("")
|
143
154
|
|
144
|
-
def
|
145
|
-
"""Show detailed statistics."""
|
146
|
-
|
147
|
-
self.stdout.write(
|
148
|
-
|
149
|
-
#
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
self.stdout.write(
|
187
|
-
previous_count = 0
|
188
|
-
for label, delta in thresholds:
|
189
|
-
threshold = now - delta
|
190
|
-
count = Currency.objects.filter(rate_updated_at__gte=threshold).count()
|
191
|
-
new_in_period = count - previous_count
|
192
|
-
self.stdout.write(f" {label}: {new_in_period} new updates ({count} total)")
|
193
|
-
previous_count = count
|
194
|
-
|
195
|
-
# Never updated
|
196
|
-
never_updated = Currency.objects.filter(rate_updated_at__isnull=True).count()
|
197
|
-
if never_updated > 0:
|
198
|
-
self.stdout.write(f" Never updated: {never_updated} currencies")
|
155
|
+
def show_detailed_stats(self):
|
156
|
+
"""Show detailed statistics breakdown."""
|
157
|
+
self.stdout.write(self.style.SUCCESS("🔍 DETAILED BREAKDOWN"))
|
158
|
+
self.stdout.write("-" * 40)
|
159
|
+
|
160
|
+
# Currency type breakdown
|
161
|
+
crypto_active = Currency.objects.filter(currency_type='crypto', is_active=True).count()
|
162
|
+
crypto_inactive = Currency.objects.filter(currency_type='crypto', is_active=False).count()
|
163
|
+
fiat_active = Currency.objects.filter(currency_type='fiat', is_active=True).count()
|
164
|
+
fiat_inactive = Currency.objects.filter(currency_type='fiat', is_active=False).count()
|
165
|
+
|
166
|
+
self.stdout.write("Currency Status:")
|
167
|
+
self.stdout.write(f" Crypto: {crypto_active} active, {crypto_inactive} inactive")
|
168
|
+
self.stdout.write(f" Fiat: {fiat_active} active, {fiat_inactive} inactive")
|
169
|
+
|
170
|
+
# Provider coverage
|
171
|
+
providers = get_provider_registry().get_available_providers()
|
172
|
+
self.stdout.write(f"\nProvider Coverage:")
|
173
|
+
|
174
|
+
for provider_name in providers:
|
175
|
+
provider_currencies = ProviderCurrency.objects.filter(
|
176
|
+
provider=provider_name,
|
177
|
+
is_active=True
|
178
|
+
).count()
|
179
|
+
self.stdout.write(f" {provider_name}: {provider_currencies} currencies")
|
180
|
+
|
181
|
+
# Payment volume by currency
|
182
|
+
payment_stats = UniversalPayment.objects.filter(
|
183
|
+
status='completed'
|
184
|
+
).values('currency__code').annotate(
|
185
|
+
count=Count('id'),
|
186
|
+
total_volume=Sum('amount_usd')
|
187
|
+
).order_by('-total_volume')[:5]
|
188
|
+
|
189
|
+
if payment_stats:
|
190
|
+
self.stdout.write(f"\nTop Payment Currencies:")
|
191
|
+
for stat in payment_stats:
|
192
|
+
currency = stat['currency__code'] or 'Unknown'
|
193
|
+
count = stat['count']
|
194
|
+
volume = stat['total_volume'] or 0
|
195
|
+
self.stdout.write(f" {currency}: {count} payments, ${intcomma(volume)}")
|
196
|
+
|
197
|
+
self.stdout.write("")
|
199
198
|
|
200
|
-
def
|
199
|
+
def show_top_currencies(self):
|
200
|
+
"""Show top currencies by various metrics."""
|
201
|
+
top_count = self.options['top']
|
202
|
+
|
203
|
+
self.stdout.write(self.style.SUCCESS(f"🏆 TOP {top_count} CURRENCIES"))
|
204
|
+
self.stdout.write("-" * 40)
|
205
|
+
|
206
|
+
# Top by payment count
|
207
|
+
top_by_payments = UniversalPayment.objects.filter(
|
208
|
+
status='completed'
|
209
|
+
).values('currency__code', 'currency__name').annotate(
|
210
|
+
payment_count=Count('id')
|
211
|
+
).order_by('-payment_count')[:top_count]
|
212
|
+
|
213
|
+
if top_by_payments:
|
214
|
+
self.stdout.write("By Payment Count:")
|
215
|
+
for i, currency in enumerate(top_by_payments, 1):
|
216
|
+
code = currency['currency__code'] or 'Unknown'
|
217
|
+
name = currency['currency__name'] or 'Unknown'
|
218
|
+
count = currency['payment_count']
|
219
|
+
self.stdout.write(f" {i:2d}. {code} ({name}): {count} payments")
|
220
|
+
|
221
|
+
# Top by volume
|
222
|
+
top_by_volume = UniversalPayment.objects.filter(
|
223
|
+
status='completed'
|
224
|
+
).values('currency__code', 'currency__name').annotate(
|
225
|
+
total_volume=Sum('amount_usd')
|
226
|
+
).order_by('-total_volume')[:top_count]
|
227
|
+
|
228
|
+
if top_by_volume:
|
229
|
+
self.stdout.write(f"\nBy Payment Volume:")
|
230
|
+
for i, currency in enumerate(top_by_volume, 1):
|
231
|
+
code = currency['currency__code'] or 'Unknown'
|
232
|
+
name = currency['currency__name'] or 'Unknown'
|
233
|
+
volume = currency['total_volume'] or 0
|
234
|
+
self.stdout.write(f" {i:2d}. {code} ({name}): ${intcomma(volume)}")
|
235
|
+
|
236
|
+
self.stdout.write("")
|
237
|
+
|
238
|
+
def check_rate_freshness(self):
|
201
239
|
"""Check for outdated exchange rates."""
|
202
|
-
|
203
|
-
self.stdout.write(
|
240
|
+
self.stdout.write(self.style.SUCCESS("🕐 RATE FRESHNESS CHECK"))
|
241
|
+
self.stdout.write("-" * 40)
|
204
242
|
|
205
243
|
now = timezone.now()
|
244
|
+
stale_threshold = now - timedelta(hours=24)
|
245
|
+
very_stale_threshold = now - timedelta(days=7)
|
246
|
+
|
247
|
+
# Check currencies with stale rates
|
248
|
+
stale_currencies = Currency.objects.filter(
|
249
|
+
updated_at__lt=stale_threshold,
|
250
|
+
is_active=True
|
251
|
+
).order_by('updated_at')
|
252
|
+
|
253
|
+
very_stale_currencies = Currency.objects.filter(
|
254
|
+
updated_at__lt=very_stale_threshold,
|
255
|
+
is_active=True
|
256
|
+
).order_by('updated_at')
|
257
|
+
|
258
|
+
fresh_currencies = Currency.objects.filter(
|
259
|
+
updated_at__gte=stale_threshold,
|
260
|
+
is_active=True
|
261
|
+
).count()
|
206
262
|
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
Q(rate_updated_at__lt=very_old_threshold) | Q(rate_updated_at__isnull=True),
|
211
|
-
)
|
212
|
-
|
213
|
-
if very_old.exists():
|
214
|
-
self.stdout.write(
|
215
|
-
self.style.ERROR(f" ❌ {very_old.count()} currencies with very old rates (>30 days)")
|
216
|
-
)
|
217
|
-
for currency in very_old[:5]:
|
218
|
-
age = self._get_rate_age(currency)
|
219
|
-
self.stdout.write(f" • {currency.code}: {age}")
|
220
|
-
if very_old.count() > 5:
|
221
|
-
self.stdout.write(f" ... and {very_old.count() - 5} more")
|
222
|
-
|
223
|
-
# Moderately outdated (7-30 days)
|
224
|
-
old_threshold = now - timedelta(days=7)
|
225
|
-
old_currencies = Currency.objects.filter(
|
226
|
-
rate_updated_at__lt=old_threshold,
|
227
|
-
rate_updated_at__gte=very_old_threshold,
|
228
|
-
)
|
229
|
-
|
230
|
-
if old_currencies.exists():
|
231
|
-
self.stdout.write(
|
232
|
-
self.style.WARNING(f" ⚠️ {old_currencies.count()} currencies with old rates (7-30 days)")
|
233
|
-
)
|
263
|
+
self.stdout.write(f"Fresh rates (< 24h): {self.style.SUCCESS(fresh_currencies)}")
|
264
|
+
self.stdout.write(f"Stale rates (> 24h): {self.style.WARNING(stale_currencies.count())}")
|
265
|
+
self.stdout.write(f"Very stale (> 7d): {self.style.ERROR(very_stale_currencies.count())}")
|
234
266
|
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
267
|
+
if very_stale_currencies.exists():
|
268
|
+
self.stdout.write(f"\n{self.style.ERROR('⚠️ VERY STALE CURRENCIES:')}")
|
269
|
+
for currency in very_stale_currencies[:10]:
|
270
|
+
age = now - currency.updated_at
|
271
|
+
self.stdout.write(f" {currency.code}: {age.days} days old")
|
240
272
|
|
241
|
-
if
|
242
|
-
self.stdout.write(
|
243
|
-
|
244
|
-
|
273
|
+
if stale_currencies.exists() and not very_stale_currencies.exists():
|
274
|
+
self.stdout.write(f"\n{self.style.WARNING('⚠️ STALE CURRENCIES:')}")
|
275
|
+
for currency in stale_currencies[:10]:
|
276
|
+
age = now - currency.updated_at
|
277
|
+
hours = int(age.total_seconds() / 3600)
|
278
|
+
self.stdout.write(f" {currency.code}: {hours} hours old")
|
245
279
|
|
246
|
-
|
247
|
-
total_active = Currency.objects.count()
|
248
|
-
if very_old.count() > 0:
|
249
|
-
self.stdout.write(f"\n💡 Recommendations:")
|
250
|
-
self.stdout.write(f" • Run: python manage.py update_currencies --force-update")
|
251
|
-
self.stdout.write(f" • Consider deactivating currencies with very old rates")
|
280
|
+
self.stdout.write("")
|
252
281
|
|
253
|
-
def
|
254
|
-
"""
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
282
|
+
def show_provider_stats(self, provider_name: str):
|
283
|
+
"""Show statistics for specific provider."""
|
284
|
+
self.stdout.write(self.style.SUCCESS(f"🏢 PROVIDER STATISTICS: {provider_name.upper()}"))
|
285
|
+
self.stdout.write("-" * 40)
|
286
|
+
|
287
|
+
# Provider currency stats
|
288
|
+
provider_currencies = ProviderCurrency.objects.filter(provider=provider_name)
|
289
|
+
active_provider_currencies = provider_currencies.filter(is_active=True)
|
290
|
+
|
291
|
+
# Payment stats for this provider
|
292
|
+
provider_payments = UniversalPayment.objects.filter(provider=provider_name)
|
293
|
+
completed_payments = provider_payments.filter(status='completed')
|
294
|
+
|
295
|
+
# Volume stats
|
296
|
+
total_volume = completed_payments.aggregate(Sum('amount_usd'))['amount_usd__sum'] or 0
|
297
|
+
avg_payment = completed_payments.aggregate(Avg('amount_usd'))['amount_usd__avg'] or 0
|
298
|
+
|
299
|
+
stats = [
|
300
|
+
("Total Currencies", provider_currencies.count()),
|
301
|
+
("Active Currencies", active_provider_currencies.count()),
|
302
|
+
("Total Payments", provider_payments.count()),
|
303
|
+
("Completed Payments", completed_payments.count()),
|
304
|
+
("Total Volume", f"${intcomma(total_volume)}"),
|
305
|
+
("Average Payment", f"${intcomma(avg_payment):.2f}"),
|
306
|
+
]
|
307
|
+
|
308
|
+
for label, value in stats:
|
309
|
+
self.stdout.write(f"{label:<20}: {self.style.WARNING(str(value))}")
|
310
|
+
|
311
|
+
# Top currencies for this provider
|
312
|
+
top_currencies = completed_payments.values(
|
313
|
+
'currency__code', 'currency__name'
|
314
|
+
).annotate(
|
315
|
+
count=Count('id'),
|
316
|
+
volume=Sum('amount_usd')
|
317
|
+
).order_by('-volume')[:5]
|
318
|
+
|
319
|
+
if top_currencies:
|
320
|
+
self.stdout.write(f"\nTop Currencies:")
|
321
|
+
for currency in top_currencies:
|
322
|
+
code = currency['currency__code'] or 'Unknown'
|
323
|
+
count = currency['count']
|
324
|
+
volume = currency['volume'] or 0
|
325
|
+
self.stdout.write(f" {code}: {count} payments, ${intcomma(volume)}")
|
326
|
+
|
327
|
+
self.stdout.write("")
|
270
328
|
|
271
|
-
def
|
272
|
-
"""Export
|
329
|
+
def export_to_csv(self, filename: str):
|
330
|
+
"""Export statistics to CSV file."""
|
273
331
|
import csv
|
274
|
-
|
275
|
-
self.stdout.write(f"\n📁 Exporting to {filename}...")
|
332
|
+
from pathlib import Path
|
276
333
|
|
277
334
|
try:
|
335
|
+
# Collect all statistics
|
336
|
+
currencies = Currency.objects.select_related().all()
|
337
|
+
|
278
338
|
with open(filename, 'w', newline='', encoding='utf-8') as csvfile:
|
279
339
|
writer = csv.writer(csvfile)
|
280
340
|
|
281
|
-
#
|
341
|
+
# Write header
|
282
342
|
writer.writerow([
|
283
|
-
'
|
343
|
+
'Code', 'Name', 'Type', 'Active', 'Created', 'Updated',
|
344
|
+
'Payment Count', 'Total Volume USD'
|
284
345
|
])
|
285
346
|
|
286
|
-
#
|
287
|
-
currencies = Currency.objects.all().order_by('code')
|
347
|
+
# Write currency data
|
288
348
|
for currency in currencies:
|
349
|
+
# Get payment stats for this currency
|
350
|
+
payments = UniversalPayment.objects.filter(
|
351
|
+
currency=currency,
|
352
|
+
status='completed'
|
353
|
+
)
|
354
|
+
payment_count = payments.count()
|
355
|
+
total_volume = payments.aggregate(Sum('amount_usd'))['amount_usd__sum'] or 0
|
356
|
+
|
289
357
|
writer.writerow([
|
290
358
|
currency.code,
|
291
359
|
currency.name,
|
292
360
|
currency.currency_type,
|
293
|
-
currency.
|
294
|
-
currency.
|
361
|
+
currency.is_active,
|
362
|
+
currency.created_at.strftime('%Y-%m-%d'),
|
363
|
+
currency.updated_at.strftime('%Y-%m-%d'),
|
364
|
+
payment_count,
|
365
|
+
f"{total_volume:.2f}"
|
295
366
|
])
|
296
367
|
|
297
368
|
self.stdout.write(
|
298
|
-
self.style.SUCCESS(f"
|
369
|
+
self.style.SUCCESS(f"✅ Statistics exported to: {filename}")
|
299
370
|
)
|
300
371
|
|
301
372
|
except Exception as e:
|
373
|
+
logger.error(f"Failed to export CSV: {e}")
|
302
374
|
self.stdout.write(
|
303
|
-
self.style.ERROR(f"
|
375
|
+
self.style.ERROR(f"❌ Failed to export CSV: {e}")
|
304
376
|
)
|