django-cfg 1.3.5__py3-none-any.whl → 1.3.9__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/accounts/admin/__init__.py +24 -8
- django_cfg/apps/accounts/admin/activity_admin.py +146 -0
- django_cfg/apps/accounts/admin/filters.py +98 -22
- django_cfg/apps/accounts/admin/group_admin.py +86 -0
- django_cfg/apps/accounts/admin/inlines.py +42 -13
- django_cfg/apps/accounts/admin/otp_admin.py +115 -0
- django_cfg/apps/accounts/admin/registration_admin.py +173 -0
- django_cfg/apps/accounts/admin/resources.py +123 -19
- django_cfg/apps/accounts/admin/twilio_admin.py +327 -0
- django_cfg/apps/accounts/admin/user_admin.py +362 -0
- django_cfg/apps/agents/admin/__init__.py +17 -4
- django_cfg/apps/agents/admin/execution_admin.py +204 -183
- django_cfg/apps/agents/admin/registry_admin.py +230 -255
- django_cfg/apps/agents/admin/toolsets_admin.py +274 -321
- django_cfg/apps/agents/core/__init__.py +1 -1
- django_cfg/apps/agents/core/django_agent.py +221 -0
- django_cfg/apps/agents/core/exceptions.py +14 -0
- django_cfg/apps/agents/core/orchestrator.py +18 -3
- django_cfg/apps/knowbase/admin/__init__.py +1 -1
- django_cfg/apps/knowbase/admin/archive_admin.py +352 -640
- django_cfg/apps/knowbase/admin/chat_admin.py +258 -192
- django_cfg/apps/knowbase/admin/document_admin.py +269 -262
- django_cfg/apps/knowbase/admin/external_data_admin.py +271 -489
- django_cfg/apps/knowbase/config/settings.py +21 -4
- django_cfg/apps/knowbase/views/chat_views.py +3 -0
- django_cfg/apps/leads/admin/__init__.py +3 -1
- django_cfg/apps/leads/admin/leads_admin.py +235 -35
- django_cfg/apps/maintenance/admin/__init__.py +2 -2
- django_cfg/apps/maintenance/admin/api_key_admin.py +125 -63
- django_cfg/apps/maintenance/admin/log_admin.py +143 -61
- django_cfg/apps/maintenance/admin/scheduled_admin.py +212 -301
- django_cfg/apps/maintenance/admin/site_admin.py +213 -352
- django_cfg/apps/newsletter/admin/__init__.py +29 -2
- django_cfg/apps/newsletter/admin/newsletter_admin.py +531 -193
- django_cfg/apps/payments/admin/__init__.py +18 -27
- django_cfg/apps/payments/admin/api_keys_admin.py +179 -546
- django_cfg/apps/payments/admin/balance_admin.py +166 -632
- django_cfg/apps/payments/admin/currencies_admin.py +235 -607
- django_cfg/apps/payments/admin/endpoint_groups_admin.py +127 -0
- django_cfg/apps/payments/admin/filters.py +83 -3
- django_cfg/apps/payments/admin/networks_admin.py +258 -0
- django_cfg/apps/payments/admin/payments_admin.py +171 -461
- django_cfg/apps/payments/admin/subscriptions_admin.py +119 -636
- django_cfg/apps/payments/admin/tariffs_admin.py +248 -0
- django_cfg/apps/payments/admin_interface/serializers/payment_serializers.py +105 -34
- django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +12 -16
- django_cfg/apps/payments/admin_interface/views/__init__.py +2 -0
- django_cfg/apps/payments/admin_interface/views/api/webhook_admin.py +13 -18
- django_cfg/apps/payments/management/commands/manage_currencies.py +236 -274
- django_cfg/apps/payments/management/commands/manage_providers.py +4 -1
- django_cfg/apps/payments/middleware/api_access.py +32 -6
- django_cfg/apps/payments/migrations/0002_currency_usd_rate_currency_usd_rate_updated_at.py +26 -0
- django_cfg/apps/payments/migrations/0003_remove_provider_currency_fields.py +28 -0
- django_cfg/apps/payments/migrations/0004_add_reserved_usd_field.py +30 -0
- django_cfg/apps/payments/models/balance.py +12 -0
- django_cfg/apps/payments/models/currencies.py +106 -32
- django_cfg/apps/payments/models/managers/currency_managers.py +65 -0
- django_cfg/apps/payments/services/core/currency_service.py +35 -28
- django_cfg/apps/payments/services/core/payment_service.py +1 -1
- django_cfg/apps/payments/services/providers/__init__.py +3 -0
- django_cfg/apps/payments/services/providers/base.py +95 -39
- django_cfg/apps/payments/services/providers/models/__init__.py +40 -0
- django_cfg/apps/payments/services/providers/models/base.py +122 -0
- django_cfg/apps/payments/services/providers/models/providers.py +87 -0
- django_cfg/apps/payments/services/providers/models/universal.py +48 -0
- django_cfg/apps/payments/services/providers/nowpayments/__init__.py +31 -0
- django_cfg/apps/payments/services/providers/nowpayments/config.py +70 -0
- django_cfg/apps/payments/services/providers/nowpayments/models.py +150 -0
- django_cfg/apps/payments/services/providers/nowpayments/parsers.py +879 -0
- django_cfg/apps/payments/services/providers/{nowpayments.py → nowpayments/provider.py} +240 -209
- django_cfg/apps/payments/services/providers/nowpayments/sync.py +196 -0
- django_cfg/apps/payments/services/providers/registry.py +4 -32
- django_cfg/apps/payments/services/providers/sync_service.py +277 -0
- django_cfg/apps/payments/static/payments/js/api-client.js +23 -5
- django_cfg/apps/payments/static/payments/js/payment-form.js +65 -8
- django_cfg/apps/payments/tasks/__init__.py +39 -0
- django_cfg/apps/payments/tasks/types.py +73 -0
- django_cfg/apps/payments/tasks/usage_tracking.py +308 -0
- django_cfg/apps/payments/templates/admin/payments/_components/dashboard_header.html +23 -0
- django_cfg/apps/payments/templates/admin/payments/_components/stats_card.html +25 -0
- django_cfg/apps/payments/templates/admin/payments/_components/stats_grid.html +16 -0
- django_cfg/apps/payments/templates/admin/payments/apikey/change_list.html +39 -0
- django_cfg/apps/payments/templates/admin/payments/balance/change_list.html +50 -0
- django_cfg/apps/payments/templates/admin/payments/currency/change_list.html +40 -0
- django_cfg/apps/payments/templates/admin/payments/payment/change_list.html +48 -0
- django_cfg/apps/payments/templates/admin/payments/subscription/change_list.html +48 -0
- django_cfg/apps/payments/urls_admin.py +1 -1
- django_cfg/apps/payments/views/api/currencies.py +5 -5
- django_cfg/apps/payments/views/overview/services.py +2 -2
- django_cfg/apps/payments/views/serializers/currencies.py +4 -3
- django_cfg/apps/support/admin/__init__.py +10 -1
- django_cfg/apps/support/admin/support_admin.py +338 -141
- django_cfg/apps/tasks/admin/__init__.py +11 -0
- django_cfg/apps/tasks/admin/tasks_admin.py +430 -0
- django_cfg/apps/urls.py +1 -2
- django_cfg/config.py +1 -1
- django_cfg/core/config.py +10 -5
- django_cfg/core/generation.py +1 -1
- django_cfg/management/commands/__init__.py +13 -1
- django_cfg/management/commands/app_agent_diagnose.py +470 -0
- django_cfg/management/commands/app_agent_generate.py +342 -0
- django_cfg/management/commands/app_agent_info.py +308 -0
- django_cfg/management/commands/migrate_all.py +9 -3
- django_cfg/management/commands/migrator.py +11 -6
- django_cfg/management/commands/rundramatiq.py +3 -2
- django_cfg/middleware/__init__.py +0 -2
- django_cfg/models/api_keys.py +115 -0
- django_cfg/modules/django_admin/__init__.py +64 -0
- django_cfg/modules/django_admin/decorators/__init__.py +13 -0
- django_cfg/modules/django_admin/decorators/actions.py +106 -0
- django_cfg/modules/django_admin/decorators/display.py +106 -0
- django_cfg/modules/django_admin/mixins/__init__.py +14 -0
- django_cfg/modules/django_admin/mixins/display_mixin.py +81 -0
- django_cfg/modules/django_admin/mixins/optimization_mixin.py +41 -0
- django_cfg/modules/django_admin/mixins/standalone_actions_mixin.py +202 -0
- django_cfg/modules/django_admin/models/__init__.py +20 -0
- django_cfg/modules/django_admin/models/action_models.py +33 -0
- django_cfg/modules/django_admin/models/badge_models.py +20 -0
- django_cfg/modules/django_admin/models/base.py +26 -0
- django_cfg/modules/django_admin/models/display_models.py +31 -0
- django_cfg/modules/django_admin/utils/badges.py +159 -0
- django_cfg/modules/django_admin/utils/displays.py +247 -0
- django_cfg/modules/django_app_agent/__init__.py +87 -0
- django_cfg/modules/django_app_agent/agents/__init__.py +40 -0
- django_cfg/modules/django_app_agent/agents/base/__init__.py +24 -0
- django_cfg/modules/django_app_agent/agents/base/agent.py +354 -0
- django_cfg/modules/django_app_agent/agents/base/context.py +236 -0
- django_cfg/modules/django_app_agent/agents/base/executor.py +430 -0
- django_cfg/modules/django_app_agent/agents/generation/__init__.py +12 -0
- django_cfg/modules/django_app_agent/agents/generation/app_generator/__init__.py +15 -0
- django_cfg/modules/django_app_agent/agents/generation/app_generator/config_validator.py +147 -0
- django_cfg/modules/django_app_agent/agents/generation/app_generator/main.py +99 -0
- django_cfg/modules/django_app_agent/agents/generation/app_generator/models.py +32 -0
- django_cfg/modules/django_app_agent/agents/generation/app_generator/prompt_manager.py +290 -0
- django_cfg/modules/django_app_agent/agents/interfaces.py +376 -0
- django_cfg/modules/django_app_agent/core/__init__.py +33 -0
- django_cfg/modules/django_app_agent/core/config.py +300 -0
- django_cfg/modules/django_app_agent/core/exceptions.py +359 -0
- django_cfg/modules/django_app_agent/models/__init__.py +71 -0
- django_cfg/modules/django_app_agent/models/base.py +283 -0
- django_cfg/modules/django_app_agent/models/context.py +496 -0
- django_cfg/modules/django_app_agent/models/enums.py +481 -0
- django_cfg/modules/django_app_agent/models/requests.py +500 -0
- django_cfg/modules/django_app_agent/models/responses.py +585 -0
- django_cfg/modules/django_app_agent/pytest.ini +6 -0
- django_cfg/modules/django_app_agent/services/__init__.py +42 -0
- django_cfg/modules/django_app_agent/services/app_generator/__init__.py +30 -0
- django_cfg/modules/django_app_agent/services/app_generator/ai_integration.py +133 -0
- django_cfg/modules/django_app_agent/services/app_generator/context.py +40 -0
- django_cfg/modules/django_app_agent/services/app_generator/main.py +202 -0
- django_cfg/modules/django_app_agent/services/app_generator/structure.py +316 -0
- django_cfg/modules/django_app_agent/services/app_generator/validation.py +125 -0
- django_cfg/modules/django_app_agent/services/base.py +437 -0
- django_cfg/modules/django_app_agent/services/context_builder/__init__.py +34 -0
- django_cfg/modules/django_app_agent/services/context_builder/code_extractor.py +141 -0
- django_cfg/modules/django_app_agent/services/context_builder/context_generator.py +276 -0
- django_cfg/modules/django_app_agent/services/context_builder/main.py +272 -0
- django_cfg/modules/django_app_agent/services/context_builder/models.py +40 -0
- django_cfg/modules/django_app_agent/services/context_builder/pattern_analyzer.py +85 -0
- django_cfg/modules/django_app_agent/services/project_scanner/__init__.py +31 -0
- django_cfg/modules/django_app_agent/services/project_scanner/app_discovery.py +311 -0
- django_cfg/modules/django_app_agent/services/project_scanner/main.py +221 -0
- django_cfg/modules/django_app_agent/services/project_scanner/models.py +59 -0
- django_cfg/modules/django_app_agent/services/project_scanner/pattern_detection.py +94 -0
- django_cfg/modules/django_app_agent/services/questioning_service/__init__.py +28 -0
- django_cfg/modules/django_app_agent/services/questioning_service/main.py +273 -0
- django_cfg/modules/django_app_agent/services/questioning_service/models.py +111 -0
- django_cfg/modules/django_app_agent/services/questioning_service/question_generator.py +251 -0
- django_cfg/modules/django_app_agent/services/questioning_service/response_processor.py +347 -0
- django_cfg/modules/django_app_agent/services/questioning_service/session_manager.py +356 -0
- django_cfg/modules/django_app_agent/services/report_service.py +332 -0
- django_cfg/modules/django_app_agent/services/template_manager/__init__.py +18 -0
- django_cfg/modules/django_app_agent/services/template_manager/jinja_engine.py +236 -0
- django_cfg/modules/django_app_agent/services/template_manager/main.py +159 -0
- django_cfg/modules/django_app_agent/services/template_manager/models.py +36 -0
- django_cfg/modules/django_app_agent/services/template_manager/template_loader.py +100 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/admin.py.j2 +105 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/apps.py.j2 +31 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/cfg_config.py.j2 +44 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/cfg_module.py.j2 +81 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/forms.py.j2 +107 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/models.py.j2 +139 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/serializers.py.j2 +91 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/tests.py.j2 +195 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/urls.py.j2 +35 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/views.py.j2 +211 -0
- django_cfg/modules/django_app_agent/services/template_manager/variable_processor.py +200 -0
- django_cfg/modules/django_app_agent/services/validation_service/__init__.py +25 -0
- django_cfg/modules/django_app_agent/services/validation_service/django_validator.py +333 -0
- django_cfg/modules/django_app_agent/services/validation_service/main.py +242 -0
- django_cfg/modules/django_app_agent/services/validation_service/models.py +66 -0
- django_cfg/modules/django_app_agent/services/validation_service/quality_validator.py +352 -0
- django_cfg/modules/django_app_agent/services/validation_service/security_validator.py +272 -0
- django_cfg/modules/django_app_agent/services/validation_service/syntax_validator.py +203 -0
- django_cfg/modules/django_app_agent/ui/__init__.py +25 -0
- django_cfg/modules/django_app_agent/ui/cli.py +419 -0
- django_cfg/modules/django_app_agent/ui/rich_components.py +622 -0
- django_cfg/modules/django_app_agent/utils/__init__.py +38 -0
- django_cfg/modules/django_app_agent/utils/logging.py +360 -0
- django_cfg/modules/django_app_agent/utils/validation.py +417 -0
- django_cfg/modules/django_currency/__init__.py +2 -2
- django_cfg/modules/django_currency/clients/__init__.py +2 -2
- django_cfg/modules/django_currency/clients/hybrid_client.py +587 -0
- django_cfg/modules/django_currency/core/converter.py +12 -12
- django_cfg/modules/django_currency/database/__init__.py +2 -2
- django_cfg/modules/django_currency/database/database_loader.py +93 -42
- django_cfg/modules/django_llm/llm/client.py +10 -2
- django_cfg/modules/django_unfold/callbacks/actions.py +1 -1
- django_cfg/modules/django_unfold/callbacks/statistics.py +1 -1
- django_cfg/modules/django_unfold/dashboard.py +14 -13
- django_cfg/modules/django_unfold/models/config.py +1 -1
- django_cfg/registry/core.py +3 -0
- django_cfg/registry/third_party.py +2 -2
- django_cfg/template_archive/django_sample.zip +0 -0
- {django_cfg-1.3.5.dist-info → django_cfg-1.3.9.dist-info}/METADATA +2 -1
- {django_cfg-1.3.5.dist-info → django_cfg-1.3.9.dist-info}/RECORD +224 -118
- django_cfg/apps/accounts/admin/activity.py +0 -96
- django_cfg/apps/accounts/admin/group.py +0 -17
- django_cfg/apps/accounts/admin/otp.py +0 -59
- django_cfg/apps/accounts/admin/registration_source.py +0 -97
- django_cfg/apps/accounts/admin/twilio_response.py +0 -227
- django_cfg/apps/accounts/admin/user.py +0 -300
- django_cfg/apps/agents/core/agent.py +0 -281
- django_cfg/apps/payments/admin_interface/old/payments/base.html +0 -175
- django_cfg/apps/payments/admin_interface/old/payments/components/dev_tool_card.html +0 -125
- django_cfg/apps/payments/admin_interface/old/payments/components/loading_spinner.html +0 -16
- django_cfg/apps/payments/admin_interface/old/payments/components/ngrok_status_card.html +0 -113
- django_cfg/apps/payments/admin_interface/old/payments/components/notification.html +0 -27
- django_cfg/apps/payments/admin_interface/old/payments/components/provider_card.html +0 -86
- django_cfg/apps/payments/admin_interface/old/payments/components/status_card.html +0 -35
- django_cfg/apps/payments/admin_interface/old/payments/currency_converter.html +0 -382
- django_cfg/apps/payments/admin_interface/old/payments/payment_dashboard.html +0 -309
- django_cfg/apps/payments/admin_interface/old/payments/payment_form.html +0 -303
- django_cfg/apps/payments/admin_interface/old/payments/payment_list.html +0 -382
- django_cfg/apps/payments/admin_interface/old/payments/payment_status.html +0 -500
- django_cfg/apps/payments/admin_interface/old/payments/webhook_dashboard.html +0 -518
- django_cfg/apps/payments/admin_interface/old/static/payments/css/components.css +0 -619
- django_cfg/apps/payments/admin_interface/old/static/payments/css/dashboard.css +0 -188
- django_cfg/apps/payments/admin_interface/old/static/payments/js/components.js +0 -545
- django_cfg/apps/payments/admin_interface/old/static/payments/js/ngrok-status.js +0 -163
- django_cfg/apps/payments/admin_interface/old/static/payments/js/utils.js +0 -412
- django_cfg/apps/tasks/admin.py +0 -320
- django_cfg/middleware/static_nocache.py +0 -55
- django_cfg/modules/django_currency/clients/yahoo_client.py +0 -157
- /django_cfg/modules/{django_unfold → django_admin}/icons/README.md +0 -0
- /django_cfg/modules/{django_unfold → django_admin}/icons/__init__.py +0 -0
- /django_cfg/modules/{django_unfold → django_admin}/icons/constants.py +0 -0
- /django_cfg/modules/{django_unfold → django_admin}/icons/generate_icons.py +0 -0
- {django_cfg-1.3.5.dist-info → django_cfg-1.3.9.dist-info}/WHEEL +0 -0
- {django_cfg-1.3.5.dist-info → django_cfg-1.3.9.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.3.5.dist-info → django_cfg-1.3.9.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,196 @@
|
|
1
|
+
"""
|
2
|
+
NowPayments currency synchronization service.
|
3
|
+
|
4
|
+
Handles syncing currencies from NowPayments API to database.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from typing import Optional, Tuple, List
|
8
|
+
from django.db import transaction
|
9
|
+
from django.utils import timezone
|
10
|
+
|
11
|
+
from django_cfg.modules.django_logger import get_logger
|
12
|
+
from django_cfg.apps.payments.models import Currency, Network, ProviderCurrency
|
13
|
+
from ..models import UniversalCurrency, CurrencySyncResult
|
14
|
+
from .config import NowPaymentsConfig as Config
|
15
|
+
|
16
|
+
logger = get_logger("nowpayments_sync")
|
17
|
+
|
18
|
+
|
19
|
+
class NowPaymentsCurrencySync:
|
20
|
+
"""Service for synchronizing NowPayments currencies to database."""
|
21
|
+
|
22
|
+
def __init__(self, provider_name: str = 'nowpayments'):
|
23
|
+
"""Initialize currency sync service."""
|
24
|
+
self.provider_name = provider_name
|
25
|
+
|
26
|
+
def sync_currencies_to_db(self, currencies: List[UniversalCurrency]) -> CurrencySyncResult:
|
27
|
+
"""
|
28
|
+
Sync universal currencies to database.
|
29
|
+
|
30
|
+
Args:
|
31
|
+
currencies: List of universal currencies from provider
|
32
|
+
|
33
|
+
Returns:
|
34
|
+
CurrencySyncResult: Sync operation results
|
35
|
+
"""
|
36
|
+
result = CurrencySyncResult(total_processed=len(currencies))
|
37
|
+
|
38
|
+
try:
|
39
|
+
with transaction.atomic():
|
40
|
+
for currency in currencies:
|
41
|
+
try:
|
42
|
+
self._sync_single_currency(currency, result)
|
43
|
+
except Exception as e:
|
44
|
+
error_msg = f"Failed to sync {currency.provider_currency_code}: {e}"
|
45
|
+
logger.error(error_msg)
|
46
|
+
result.errors.append(error_msg)
|
47
|
+
|
48
|
+
logger.info(
|
49
|
+
f"Currency sync completed: {result.currencies_created} created, "
|
50
|
+
f"{result.currencies_updated} updated, "
|
51
|
+
f"{result.provider_currencies_created} provider currencies created, "
|
52
|
+
f"{len(result.errors)} errors"
|
53
|
+
)
|
54
|
+
|
55
|
+
return result
|
56
|
+
|
57
|
+
except Exception as e:
|
58
|
+
logger.error(f"Currency sync transaction failed: {e}")
|
59
|
+
result.errors.append(f"Transaction failed: {e}")
|
60
|
+
return result
|
61
|
+
|
62
|
+
def _sync_single_currency(self, currency: UniversalCurrency, result: CurrencySyncResult):
|
63
|
+
"""Sync a single currency to database."""
|
64
|
+
|
65
|
+
# 1. Ensure base currency exists
|
66
|
+
base_currency, currency_created = self._get_or_create_currency(currency)
|
67
|
+
if currency_created:
|
68
|
+
result.currencies_created += 1
|
69
|
+
else:
|
70
|
+
# Update existing currency if needed
|
71
|
+
updated = self._update_currency_if_needed(base_currency, currency)
|
72
|
+
if updated:
|
73
|
+
result.currencies_updated += 1
|
74
|
+
|
75
|
+
# 2. Ensure network exists (if applicable)
|
76
|
+
network = None
|
77
|
+
if currency.network_code:
|
78
|
+
network, network_created = self._get_or_create_network(currency, base_currency)
|
79
|
+
if network_created:
|
80
|
+
result.networks_created += 1
|
81
|
+
|
82
|
+
# 3. Create or update provider currency
|
83
|
+
provider_currency, pc_created = self._get_or_create_provider_currency(
|
84
|
+
currency, base_currency, network
|
85
|
+
)
|
86
|
+
|
87
|
+
if pc_created:
|
88
|
+
result.provider_currencies_created += 1
|
89
|
+
else:
|
90
|
+
# Update existing provider currency
|
91
|
+
updated = self._update_provider_currency_if_needed(provider_currency, currency)
|
92
|
+
if updated:
|
93
|
+
result.provider_currencies_updated += 1
|
94
|
+
|
95
|
+
def _get_or_create_currency(self, currency: UniversalCurrency) -> Tuple[Currency, bool]:
|
96
|
+
"""Get or create base currency."""
|
97
|
+
|
98
|
+
defaults = {
|
99
|
+
'name': currency.name,
|
100
|
+
'currency_type': currency.currency_type,
|
101
|
+
'is_active': currency.is_enabled,
|
102
|
+
'usd_rate': 1.0, # Will be updated by rate sync
|
103
|
+
'usd_rate_updated_at': None,
|
104
|
+
'decimal_places': self._get_decimal_places(currency),
|
105
|
+
}
|
106
|
+
|
107
|
+
return Currency.objects.get_or_create(
|
108
|
+
code=currency.base_currency_code,
|
109
|
+
defaults=defaults
|
110
|
+
)
|
111
|
+
|
112
|
+
def _update_currency_if_needed(self, base_currency: Currency, currency: UniversalCurrency) -> bool:
|
113
|
+
"""Update currency if needed."""
|
114
|
+
updated = False
|
115
|
+
|
116
|
+
# Always update name to use the proper generated name
|
117
|
+
if base_currency.name != currency.name:
|
118
|
+
base_currency.name = currency.name
|
119
|
+
updated = True
|
120
|
+
|
121
|
+
# Update activity status
|
122
|
+
if base_currency.is_active != currency.is_enabled:
|
123
|
+
base_currency.is_active = currency.is_enabled
|
124
|
+
updated = True
|
125
|
+
|
126
|
+
if updated:
|
127
|
+
base_currency.save()
|
128
|
+
|
129
|
+
return updated
|
130
|
+
|
131
|
+
def _get_or_create_network(self, currency: UniversalCurrency, base_currency: Currency) -> Tuple[Network, bool]:
|
132
|
+
"""Get or create network for currency."""
|
133
|
+
|
134
|
+
network_name = Config.get_network_name(currency.network_code)
|
135
|
+
|
136
|
+
defaults = {
|
137
|
+
'name': network_name,
|
138
|
+
'native_currency': base_currency,
|
139
|
+
'is_active': True,
|
140
|
+
'confirmation_blocks': Config.get_confirmation_blocks(currency.network_code),
|
141
|
+
}
|
142
|
+
|
143
|
+
return Network.objects.get_or_create(
|
144
|
+
code=currency.network_code,
|
145
|
+
defaults=defaults
|
146
|
+
)
|
147
|
+
|
148
|
+
def _get_or_create_provider_currency(
|
149
|
+
self,
|
150
|
+
currency: UniversalCurrency,
|
151
|
+
base_currency: Currency,
|
152
|
+
network: Optional[Network]
|
153
|
+
) -> Tuple[ProviderCurrency, bool]:
|
154
|
+
"""Get or create provider currency."""
|
155
|
+
|
156
|
+
defaults = {
|
157
|
+
'currency': base_currency,
|
158
|
+
'network': network,
|
159
|
+
'is_enabled': currency.is_enabled,
|
160
|
+
}
|
161
|
+
|
162
|
+
return ProviderCurrency.objects.get_or_create(
|
163
|
+
provider=self.provider_name,
|
164
|
+
provider_currency_code=currency.provider_currency_code,
|
165
|
+
defaults=defaults
|
166
|
+
)
|
167
|
+
|
168
|
+
def _update_provider_currency_if_needed(
|
169
|
+
self,
|
170
|
+
provider_currency: ProviderCurrency,
|
171
|
+
currency: UniversalCurrency
|
172
|
+
) -> bool:
|
173
|
+
"""Update provider currency if needed."""
|
174
|
+
updated = False
|
175
|
+
|
176
|
+
# Update enabled status
|
177
|
+
if provider_currency.is_enabled != currency.is_enabled:
|
178
|
+
provider_currency.is_enabled = currency.is_enabled
|
179
|
+
updated = True
|
180
|
+
|
181
|
+
# Update timestamps
|
182
|
+
if updated:
|
183
|
+
provider_currency.updated_at = timezone.now()
|
184
|
+
provider_currency.save()
|
185
|
+
|
186
|
+
return updated
|
187
|
+
|
188
|
+
def _get_decimal_places(self, currency: UniversalCurrency) -> int:
|
189
|
+
"""Get appropriate decimal places for currency."""
|
190
|
+
if currency.currency_type == 'fiat':
|
191
|
+
return 2
|
192
|
+
elif currency.is_stable:
|
193
|
+
return 6 # Stablecoins
|
194
|
+
else:
|
195
|
+
return 8 # Regular crypto
|
196
|
+
|
@@ -4,44 +4,16 @@ Provider registry for the Universal Payment System v2.0.
|
|
4
4
|
Centralized management of payment providers with health monitoring and fallbacks.
|
5
5
|
"""
|
6
6
|
|
7
|
-
from enum import Enum
|
8
7
|
from typing import Dict, List, Optional, Type, Any
|
9
8
|
from django_cfg.modules.django_logger import get_logger
|
10
9
|
# ConfigService removed - using direct Constance access
|
11
10
|
from ..types import ServiceOperationResult
|
12
|
-
from .base import BaseProvider
|
13
|
-
from .
|
11
|
+
from .base import BaseProvider
|
12
|
+
from .models import ProviderConfig, ProviderEnum
|
13
|
+
from .nowpayments import NowPaymentsProvider, NowPaymentsProviderConfig
|
14
14
|
|
15
15
|
logger = get_logger("provider_registry")
|
16
16
|
|
17
|
-
|
18
|
-
# Provider enums
|
19
|
-
class ProviderEnum(Enum):
|
20
|
-
NOWPAYMENTS = "nowpayments"
|
21
|
-
CRYPTAPI = "cryptapi"
|
22
|
-
STRIPE = "stripe"
|
23
|
-
CRYPTOMUS = "cryptomus"
|
24
|
-
|
25
|
-
@classmethod
|
26
|
-
def get_crypto_providers(cls):
|
27
|
-
"""Get list of crypto provider values."""
|
28
|
-
return [cls.NOWPAYMENTS.value, cls.CRYPTAPI.value, cls.CRYPTOMUS.value]
|
29
|
-
|
30
|
-
@classmethod
|
31
|
-
def get_fiat_providers(cls):
|
32
|
-
"""Get list of fiat provider values."""
|
33
|
-
return [cls.STRIPE.value]
|
34
|
-
|
35
|
-
@classmethod
|
36
|
-
def get_priority_order(cls):
|
37
|
-
"""Get list of provider values in priority order."""
|
38
|
-
return [
|
39
|
-
cls.NOWPAYMENTS.value,
|
40
|
-
cls.CRYPTAPI.value,
|
41
|
-
cls.STRIPE.value,
|
42
|
-
cls.CRYPTOMUS.value
|
43
|
-
]
|
44
|
-
|
45
17
|
class ProviderRegistry:
|
46
18
|
"""
|
47
19
|
Registry for managing payment providers.
|
@@ -62,7 +34,7 @@ class ProviderRegistry:
|
|
62
34
|
ProviderEnum.NOWPAYMENTS.value: NowPaymentsProvider,
|
63
35
|
}
|
64
36
|
self._provider_configs: Dict[str, Type[ProviderConfig]] = {
|
65
|
-
ProviderEnum.NOWPAYMENTS.value:
|
37
|
+
ProviderEnum.NOWPAYMENTS.value: NowPaymentsProviderConfig,
|
66
38
|
}
|
67
39
|
|
68
40
|
self._health_status: Dict[str, bool] = {}
|
@@ -0,0 +1,277 @@
|
|
1
|
+
"""
|
2
|
+
Universal provider synchronization service.
|
3
|
+
|
4
|
+
Handles synchronization of currencies from all payment providers to database.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from typing import List, Dict, Optional
|
8
|
+
from django.db import transaction
|
9
|
+
from django.utils import timezone
|
10
|
+
from datetime import timedelta
|
11
|
+
|
12
|
+
from django_cfg.modules.django_logger import get_logger
|
13
|
+
from django_cfg.apps.payments.models import ProviderCurrency
|
14
|
+
from .models import CurrencySyncResult
|
15
|
+
from .registry import get_provider_registry
|
16
|
+
from .base import BaseProvider
|
17
|
+
|
18
|
+
logger = get_logger("provider_sync")
|
19
|
+
|
20
|
+
|
21
|
+
class ProviderSyncService:
|
22
|
+
"""Universal service for synchronizing all payment providers."""
|
23
|
+
|
24
|
+
def __init__(self):
|
25
|
+
"""Initialize provider sync service."""
|
26
|
+
self.registry = get_provider_registry()
|
27
|
+
|
28
|
+
def sync_all_providers(
|
29
|
+
self,
|
30
|
+
force_refresh: bool = False,
|
31
|
+
provider_names: Optional[List[str]] = None
|
32
|
+
) -> Dict[str, CurrencySyncResult]:
|
33
|
+
"""
|
34
|
+
Sync currencies from all available providers.
|
35
|
+
|
36
|
+
Args:
|
37
|
+
force_refresh: Force refresh even if recently synced
|
38
|
+
provider_names: Specific providers to sync (None = all available)
|
39
|
+
|
40
|
+
Returns:
|
41
|
+
Dict[str, CurrencySyncResult]: Results by provider name
|
42
|
+
"""
|
43
|
+
logger.info("Starting universal provider synchronization")
|
44
|
+
|
45
|
+
# Get providers to sync
|
46
|
+
if provider_names:
|
47
|
+
providers_to_sync = []
|
48
|
+
for name in provider_names:
|
49
|
+
provider = self.registry.get_provider(name)
|
50
|
+
if provider:
|
51
|
+
providers_to_sync.append((name, provider))
|
52
|
+
else:
|
53
|
+
logger.warning(f"Provider {name} not available")
|
54
|
+
else:
|
55
|
+
available_providers = self.registry.get_available_providers()
|
56
|
+
providers_to_sync = [
|
57
|
+
(name, self.registry.get_provider(name))
|
58
|
+
for name in available_providers
|
59
|
+
]
|
60
|
+
|
61
|
+
if not providers_to_sync:
|
62
|
+
logger.warning("No providers available for synchronization")
|
63
|
+
return {}
|
64
|
+
|
65
|
+
# Sync each provider
|
66
|
+
results = {}
|
67
|
+
total_currencies = 0
|
68
|
+
total_errors = 0
|
69
|
+
|
70
|
+
for provider_name, provider in providers_to_sync:
|
71
|
+
try:
|
72
|
+
logger.info(f"Syncing provider: {provider_name}")
|
73
|
+
|
74
|
+
# Check if sync is needed
|
75
|
+
if not force_refresh and self._is_recently_synced(provider_name):
|
76
|
+
logger.info(f"Provider {provider_name} recently synced, skipping")
|
77
|
+
results[provider_name] = CurrencySyncResult(
|
78
|
+
total_processed=0,
|
79
|
+
errors=[f"Skipped - recently synced (use --force-refresh to override)"]
|
80
|
+
)
|
81
|
+
continue
|
82
|
+
|
83
|
+
# Perform sync
|
84
|
+
sync_result = self._sync_single_provider(provider)
|
85
|
+
results[provider_name] = sync_result
|
86
|
+
|
87
|
+
# Update stats
|
88
|
+
total_currencies += sync_result.currencies_created + sync_result.currencies_updated
|
89
|
+
total_errors += len(sync_result.errors)
|
90
|
+
|
91
|
+
# Mark sync time
|
92
|
+
self._mark_sync_time(provider_name)
|
93
|
+
|
94
|
+
logger.info(
|
95
|
+
f"Provider {provider_name} sync completed: "
|
96
|
+
f"{sync_result.currencies_created} created, "
|
97
|
+
f"{sync_result.currencies_updated} updated, "
|
98
|
+
f"{len(sync_result.errors)} errors"
|
99
|
+
)
|
100
|
+
|
101
|
+
except Exception as e:
|
102
|
+
error_msg = f"Provider {provider_name} sync failed: {e}"
|
103
|
+
logger.error(error_msg)
|
104
|
+
results[provider_name] = CurrencySyncResult(
|
105
|
+
total_processed=0,
|
106
|
+
errors=[error_msg]
|
107
|
+
)
|
108
|
+
total_errors += 1
|
109
|
+
|
110
|
+
logger.info(
|
111
|
+
f"Universal provider sync completed: "
|
112
|
+
f"{len(results)} providers processed, "
|
113
|
+
f"{total_currencies} currencies synced, "
|
114
|
+
f"{total_errors} errors"
|
115
|
+
)
|
116
|
+
|
117
|
+
return results
|
118
|
+
|
119
|
+
def sync_provider(
|
120
|
+
self,
|
121
|
+
provider_name: str,
|
122
|
+
force_refresh: bool = False
|
123
|
+
) -> CurrencySyncResult:
|
124
|
+
"""
|
125
|
+
Sync currencies from a specific provider.
|
126
|
+
|
127
|
+
Args:
|
128
|
+
provider_name: Name of provider to sync
|
129
|
+
force_refresh: Force refresh even if recently synced
|
130
|
+
|
131
|
+
Returns:
|
132
|
+
CurrencySyncResult: Sync operation result
|
133
|
+
"""
|
134
|
+
provider = self.registry.get_provider(provider_name)
|
135
|
+
if not provider:
|
136
|
+
return CurrencySyncResult(
|
137
|
+
total_processed=0,
|
138
|
+
errors=[f"Provider {provider_name} not available"]
|
139
|
+
)
|
140
|
+
|
141
|
+
# Check if sync is needed
|
142
|
+
if not force_refresh and self._is_recently_synced(provider_name):
|
143
|
+
return CurrencySyncResult(
|
144
|
+
total_processed=0,
|
145
|
+
errors=[f"Provider {provider_name} recently synced (use force_refresh=True to override)"]
|
146
|
+
)
|
147
|
+
|
148
|
+
# Perform sync
|
149
|
+
result = self._sync_single_provider(provider)
|
150
|
+
|
151
|
+
# Mark sync time
|
152
|
+
self._mark_sync_time(provider_name)
|
153
|
+
|
154
|
+
return result
|
155
|
+
|
156
|
+
def get_sync_statistics(self) -> Dict[str, any]:
|
157
|
+
"""
|
158
|
+
Get synchronization statistics for all providers.
|
159
|
+
|
160
|
+
Returns:
|
161
|
+
Dict: Statistics by provider
|
162
|
+
"""
|
163
|
+
stats = {}
|
164
|
+
|
165
|
+
for provider_name in self.registry.list_providers():
|
166
|
+
provider_stats = self._get_provider_stats(provider_name)
|
167
|
+
stats[provider_name] = provider_stats
|
168
|
+
|
169
|
+
return stats
|
170
|
+
|
171
|
+
def _sync_single_provider(self, provider: BaseProvider) -> CurrencySyncResult:
|
172
|
+
"""Sync currencies from a single provider."""
|
173
|
+
try:
|
174
|
+
logger.debug(f"Starting sync for provider: {provider.name}")
|
175
|
+
|
176
|
+
# Use provider's sync method
|
177
|
+
result = provider.sync_currencies_to_db()
|
178
|
+
|
179
|
+
logger.debug(
|
180
|
+
f"Provider {provider.name} sync result: "
|
181
|
+
f"{result.currencies_created} created, "
|
182
|
+
f"{result.currencies_updated} updated, "
|
183
|
+
f"{result.provider_currencies_created} provider currencies created"
|
184
|
+
)
|
185
|
+
|
186
|
+
return result
|
187
|
+
|
188
|
+
except Exception as e:
|
189
|
+
error_msg = f"Sync failed for provider {provider.name}: {e}"
|
190
|
+
logger.error(error_msg)
|
191
|
+
return CurrencySyncResult(
|
192
|
+
total_processed=0,
|
193
|
+
errors=[error_msg]
|
194
|
+
)
|
195
|
+
|
196
|
+
def _is_recently_synced(self, provider_name: str, hours: int = 1) -> bool:
|
197
|
+
"""Check if provider was recently synced."""
|
198
|
+
try:
|
199
|
+
# Check if any provider currencies were updated recently
|
200
|
+
recent_threshold = timezone.now() - timedelta(hours=hours)
|
201
|
+
|
202
|
+
recent_updates = ProviderCurrency.objects.filter(
|
203
|
+
provider=provider_name,
|
204
|
+
updated_at__gte=recent_threshold
|
205
|
+
).exists()
|
206
|
+
|
207
|
+
return recent_updates
|
208
|
+
|
209
|
+
except Exception as e:
|
210
|
+
logger.warning(f"Failed to check sync status for {provider_name}: {e}")
|
211
|
+
return False
|
212
|
+
|
213
|
+
def _mark_sync_time(self, provider_name: str):
|
214
|
+
"""Mark sync time for provider (could be stored in cache/database)."""
|
215
|
+
# For now, we rely on ProviderCurrency.updated_at
|
216
|
+
# In future, could store in dedicated sync log table
|
217
|
+
pass
|
218
|
+
|
219
|
+
def _get_provider_stats(self, provider_name: str) -> Dict[str, any]:
|
220
|
+
"""Get statistics for a specific provider."""
|
221
|
+
try:
|
222
|
+
from django.db.models import Count, Q
|
223
|
+
|
224
|
+
# Get provider currency stats
|
225
|
+
provider_currencies = ProviderCurrency.objects.filter(provider=provider_name)
|
226
|
+
|
227
|
+
total_currencies = provider_currencies.count()
|
228
|
+
enabled_currencies = provider_currencies.filter(is_enabled=True).count()
|
229
|
+
|
230
|
+
# Get recent activity
|
231
|
+
recent_threshold = timezone.now() - timedelta(hours=24)
|
232
|
+
recent_updates = provider_currencies.filter(
|
233
|
+
updated_at__gte=recent_threshold
|
234
|
+
).count()
|
235
|
+
|
236
|
+
# Get last sync time
|
237
|
+
last_sync = None
|
238
|
+
if provider_currencies.exists():
|
239
|
+
last_sync = provider_currencies.order_by('-updated_at').first().updated_at
|
240
|
+
|
241
|
+
return {
|
242
|
+
'total_currencies': total_currencies,
|
243
|
+
'enabled_currencies': enabled_currencies,
|
244
|
+
'disabled_currencies': total_currencies - enabled_currencies,
|
245
|
+
'recent_updates_24h': recent_updates,
|
246
|
+
'last_sync': last_sync.isoformat() if last_sync else None,
|
247
|
+
'is_recently_synced': self._is_recently_synced(provider_name)
|
248
|
+
}
|
249
|
+
|
250
|
+
except Exception as e:
|
251
|
+
logger.error(f"Failed to get stats for {provider_name}: {e}")
|
252
|
+
return {
|
253
|
+
'error': str(e),
|
254
|
+
'total_currencies': 0,
|
255
|
+
'enabled_currencies': 0,
|
256
|
+
'disabled_currencies': 0,
|
257
|
+
'recent_updates_24h': 0,
|
258
|
+
'last_sync': None,
|
259
|
+
'is_recently_synced': False
|
260
|
+
}
|
261
|
+
|
262
|
+
|
263
|
+
# Global sync service instance
|
264
|
+
_global_sync_service: Optional[ProviderSyncService] = None
|
265
|
+
|
266
|
+
|
267
|
+
def get_provider_sync_service() -> ProviderSyncService:
|
268
|
+
"""
|
269
|
+
Get global provider sync service instance.
|
270
|
+
|
271
|
+
Returns:
|
272
|
+
ProviderSyncService: Global sync service instance
|
273
|
+
"""
|
274
|
+
global _global_sync_service
|
275
|
+
if _global_sync_service is None:
|
276
|
+
_global_sync_service = ProviderSyncService()
|
277
|
+
return _global_sync_service
|
@@ -10,8 +10,22 @@ class PaymentAPIClient {
|
|
10
10
|
|
11
11
|
// Helper method to get CSRF token
|
12
12
|
getCSRFToken() {
|
13
|
-
|
14
|
-
|
13
|
+
// Try to get token from form input first
|
14
|
+
const tokenInput = document.querySelector('[name=csrfmiddlewaretoken]');
|
15
|
+
if (tokenInput && tokenInput.value) {
|
16
|
+
return tokenInput.value;
|
17
|
+
}
|
18
|
+
|
19
|
+
// Fallback to cookie
|
20
|
+
const cookieString = document.cookie;
|
21
|
+
if (cookieString.includes('csrftoken=')) {
|
22
|
+
const match = cookieString.match(/csrftoken=([^;]+)/);
|
23
|
+
if (match) {
|
24
|
+
return match[1];
|
25
|
+
}
|
26
|
+
}
|
27
|
+
|
28
|
+
return '';
|
15
29
|
}
|
16
30
|
|
17
31
|
// Generic request method
|
@@ -129,7 +143,7 @@ class PaymentAPIClient {
|
|
129
143
|
supported: (params = {}) => this.get(`${this.baseURL}/currencies/supported/`, params),
|
130
144
|
|
131
145
|
// Get currencies by provider
|
132
|
-
byProvider: (provider) => this.get(`${this.baseURL}/currencies
|
146
|
+
byProvider: (provider) => this.get(`${this.baseURL}/provider-currencies/`, { provider, page_size: 1000 }),
|
133
147
|
|
134
148
|
// Get exchange rates
|
135
149
|
rates: (params = {}) => this.get(`${this.baseURL}/currencies/rates/`, params),
|
@@ -138,7 +152,7 @@ class PaymentAPIClient {
|
|
138
152
|
convert: (from, to, amount) => this.post(`${this.baseURL}/currencies/convert/`, { from, to, amount }),
|
139
153
|
|
140
154
|
// Get provider-specific currency configurations
|
141
|
-
providerConfigs: (provider) => this.get(`${this.baseURL}/provider-currencies/`, { provider }),
|
155
|
+
providerConfigs: (provider) => this.get(`${this.baseURL}/provider-currencies/`, { provider, page_size: 1000 }),
|
142
156
|
|
143
157
|
// Get currencies grouped by provider
|
144
158
|
byProviderGrouped: () => this.get(`${this.baseURL}/provider-currencies/by_provider/`)
|
@@ -302,7 +316,7 @@ class PaymentAPIClient {
|
|
302
316
|
|
303
317
|
// Webhook test method
|
304
318
|
webhookTest: {
|
305
|
-
send: (url, eventType) => this.post(`${this.adminURL}/api/
|
319
|
+
send: (url, eventType) => this.post(`${this.adminURL}/api/webhook-test/test/`, {
|
306
320
|
webhook_url: url,
|
307
321
|
event_type: eventType
|
308
322
|
})
|
@@ -377,7 +391,11 @@ class PaymentAPIClient {
|
|
377
391
|
}
|
378
392
|
|
379
393
|
// Create global instance
|
394
|
+
console.log('🔧 PaymentAPIClient: Creating global instance...');
|
380
395
|
window.PaymentAPI = new PaymentAPIClient();
|
396
|
+
console.log('✅ PaymentAPIClient: Global instance created');
|
397
|
+
console.log('🔍 PaymentAPI.currencies:', window.PaymentAPI.currencies);
|
398
|
+
console.log('🔍 PaymentAPI.admin:', window.PaymentAPI.admin);
|
381
399
|
|
382
400
|
// Export for modules
|
383
401
|
if (typeof module !== 'undefined' && module.exports) {
|