django-cfg 1.3.7__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.
Files changed (251) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/accounts/admin/__init__.py +24 -8
  3. django_cfg/apps/accounts/admin/activity_admin.py +146 -0
  4. django_cfg/apps/accounts/admin/filters.py +98 -22
  5. django_cfg/apps/accounts/admin/group_admin.py +86 -0
  6. django_cfg/apps/accounts/admin/inlines.py +42 -13
  7. django_cfg/apps/accounts/admin/otp_admin.py +115 -0
  8. django_cfg/apps/accounts/admin/registration_admin.py +173 -0
  9. django_cfg/apps/accounts/admin/resources.py +123 -19
  10. django_cfg/apps/accounts/admin/twilio_admin.py +327 -0
  11. django_cfg/apps/accounts/admin/user_admin.py +362 -0
  12. django_cfg/apps/agents/admin/__init__.py +17 -4
  13. django_cfg/apps/agents/admin/execution_admin.py +204 -183
  14. django_cfg/apps/agents/admin/registry_admin.py +230 -255
  15. django_cfg/apps/agents/admin/toolsets_admin.py +274 -321
  16. django_cfg/apps/agents/core/__init__.py +1 -1
  17. django_cfg/apps/agents/core/django_agent.py +221 -0
  18. django_cfg/apps/agents/core/exceptions.py +14 -0
  19. django_cfg/apps/agents/core/orchestrator.py +18 -3
  20. django_cfg/apps/knowbase/admin/__init__.py +1 -1
  21. django_cfg/apps/knowbase/admin/archive_admin.py +352 -640
  22. django_cfg/apps/knowbase/admin/chat_admin.py +258 -192
  23. django_cfg/apps/knowbase/admin/document_admin.py +269 -262
  24. django_cfg/apps/knowbase/admin/external_data_admin.py +271 -489
  25. django_cfg/apps/knowbase/config/settings.py +21 -4
  26. django_cfg/apps/knowbase/views/chat_views.py +3 -0
  27. django_cfg/apps/leads/admin/__init__.py +3 -1
  28. django_cfg/apps/leads/admin/leads_admin.py +235 -35
  29. django_cfg/apps/maintenance/admin/__init__.py +2 -2
  30. django_cfg/apps/maintenance/admin/api_key_admin.py +125 -63
  31. django_cfg/apps/maintenance/admin/log_admin.py +143 -61
  32. django_cfg/apps/maintenance/admin/scheduled_admin.py +212 -301
  33. django_cfg/apps/maintenance/admin/site_admin.py +213 -352
  34. django_cfg/apps/newsletter/admin/__init__.py +29 -2
  35. django_cfg/apps/newsletter/admin/newsletter_admin.py +531 -193
  36. django_cfg/apps/payments/admin/__init__.py +18 -27
  37. django_cfg/apps/payments/admin/api_keys_admin.py +179 -546
  38. django_cfg/apps/payments/admin/balance_admin.py +166 -632
  39. django_cfg/apps/payments/admin/currencies_admin.py +235 -607
  40. django_cfg/apps/payments/admin/endpoint_groups_admin.py +127 -0
  41. django_cfg/apps/payments/admin/filters.py +83 -3
  42. django_cfg/apps/payments/admin/networks_admin.py +258 -0
  43. django_cfg/apps/payments/admin/payments_admin.py +171 -461
  44. django_cfg/apps/payments/admin/subscriptions_admin.py +119 -636
  45. django_cfg/apps/payments/admin/tariffs_admin.py +248 -0
  46. django_cfg/apps/payments/admin_interface/serializers/payment_serializers.py +105 -34
  47. django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +12 -16
  48. django_cfg/apps/payments/admin_interface/views/__init__.py +2 -0
  49. django_cfg/apps/payments/admin_interface/views/api/webhook_admin.py +13 -18
  50. django_cfg/apps/payments/management/commands/manage_currencies.py +236 -274
  51. django_cfg/apps/payments/management/commands/manage_providers.py +4 -1
  52. django_cfg/apps/payments/middleware/api_access.py +32 -6
  53. django_cfg/apps/payments/migrations/0002_currency_usd_rate_currency_usd_rate_updated_at.py +26 -0
  54. django_cfg/apps/payments/migrations/0003_remove_provider_currency_fields.py +28 -0
  55. django_cfg/apps/payments/migrations/0004_add_reserved_usd_field.py +30 -0
  56. django_cfg/apps/payments/models/balance.py +12 -0
  57. django_cfg/apps/payments/models/currencies.py +106 -32
  58. django_cfg/apps/payments/models/managers/currency_managers.py +65 -0
  59. django_cfg/apps/payments/services/core/currency_service.py +35 -28
  60. django_cfg/apps/payments/services/core/payment_service.py +1 -1
  61. django_cfg/apps/payments/services/providers/__init__.py +3 -0
  62. django_cfg/apps/payments/services/providers/base.py +95 -39
  63. django_cfg/apps/payments/services/providers/models/__init__.py +40 -0
  64. django_cfg/apps/payments/services/providers/models/base.py +122 -0
  65. django_cfg/apps/payments/services/providers/models/providers.py +87 -0
  66. django_cfg/apps/payments/services/providers/models/universal.py +48 -0
  67. django_cfg/apps/payments/services/providers/nowpayments/__init__.py +31 -0
  68. django_cfg/apps/payments/services/providers/nowpayments/config.py +70 -0
  69. django_cfg/apps/payments/services/providers/nowpayments/models.py +150 -0
  70. django_cfg/apps/payments/services/providers/nowpayments/parsers.py +879 -0
  71. django_cfg/apps/payments/services/providers/{nowpayments.py → nowpayments/provider.py} +240 -209
  72. django_cfg/apps/payments/services/providers/nowpayments/sync.py +196 -0
  73. django_cfg/apps/payments/services/providers/registry.py +4 -32
  74. django_cfg/apps/payments/services/providers/sync_service.py +277 -0
  75. django_cfg/apps/payments/static/payments/js/api-client.js +23 -5
  76. django_cfg/apps/payments/static/payments/js/payment-form.js +65 -8
  77. django_cfg/apps/payments/tasks/__init__.py +39 -0
  78. django_cfg/apps/payments/tasks/types.py +73 -0
  79. django_cfg/apps/payments/tasks/usage_tracking.py +308 -0
  80. django_cfg/apps/payments/templates/admin/payments/_components/dashboard_header.html +23 -0
  81. django_cfg/apps/payments/templates/admin/payments/_components/stats_card.html +25 -0
  82. django_cfg/apps/payments/templates/admin/payments/_components/stats_grid.html +16 -0
  83. django_cfg/apps/payments/templates/admin/payments/apikey/change_list.html +39 -0
  84. django_cfg/apps/payments/templates/admin/payments/balance/change_list.html +50 -0
  85. django_cfg/apps/payments/templates/admin/payments/currency/change_list.html +40 -0
  86. django_cfg/apps/payments/templates/admin/payments/payment/change_list.html +48 -0
  87. django_cfg/apps/payments/templates/admin/payments/subscription/change_list.html +48 -0
  88. django_cfg/apps/payments/urls_admin.py +1 -1
  89. django_cfg/apps/payments/views/api/currencies.py +5 -5
  90. django_cfg/apps/payments/views/overview/services.py +2 -2
  91. django_cfg/apps/payments/views/serializers/currencies.py +4 -3
  92. django_cfg/apps/support/admin/__init__.py +10 -1
  93. django_cfg/apps/support/admin/support_admin.py +338 -141
  94. django_cfg/apps/tasks/admin/__init__.py +11 -0
  95. django_cfg/apps/tasks/admin/tasks_admin.py +430 -0
  96. django_cfg/config.py +1 -1
  97. django_cfg/core/config.py +10 -5
  98. django_cfg/core/generation.py +1 -1
  99. django_cfg/management/commands/__init__.py +13 -1
  100. django_cfg/management/commands/app_agent_diagnose.py +470 -0
  101. django_cfg/management/commands/app_agent_generate.py +342 -0
  102. django_cfg/management/commands/app_agent_info.py +308 -0
  103. django_cfg/management/commands/migrate_all.py +9 -3
  104. django_cfg/management/commands/migrator.py +11 -6
  105. django_cfg/management/commands/rundramatiq.py +3 -2
  106. django_cfg/middleware/__init__.py +0 -2
  107. django_cfg/models/api_keys.py +115 -0
  108. django_cfg/modules/django_admin/__init__.py +64 -0
  109. django_cfg/modules/django_admin/decorators/__init__.py +13 -0
  110. django_cfg/modules/django_admin/decorators/actions.py +106 -0
  111. django_cfg/modules/django_admin/decorators/display.py +106 -0
  112. django_cfg/modules/django_admin/mixins/__init__.py +14 -0
  113. django_cfg/modules/django_admin/mixins/display_mixin.py +81 -0
  114. django_cfg/modules/django_admin/mixins/optimization_mixin.py +41 -0
  115. django_cfg/modules/django_admin/mixins/standalone_actions_mixin.py +202 -0
  116. django_cfg/modules/django_admin/models/__init__.py +20 -0
  117. django_cfg/modules/django_admin/models/action_models.py +33 -0
  118. django_cfg/modules/django_admin/models/badge_models.py +20 -0
  119. django_cfg/modules/django_admin/models/base.py +26 -0
  120. django_cfg/modules/django_admin/models/display_models.py +31 -0
  121. django_cfg/modules/django_admin/utils/badges.py +159 -0
  122. django_cfg/modules/django_admin/utils/displays.py +247 -0
  123. django_cfg/modules/django_app_agent/__init__.py +87 -0
  124. django_cfg/modules/django_app_agent/agents/__init__.py +40 -0
  125. django_cfg/modules/django_app_agent/agents/base/__init__.py +24 -0
  126. django_cfg/modules/django_app_agent/agents/base/agent.py +354 -0
  127. django_cfg/modules/django_app_agent/agents/base/context.py +236 -0
  128. django_cfg/modules/django_app_agent/agents/base/executor.py +430 -0
  129. django_cfg/modules/django_app_agent/agents/generation/__init__.py +12 -0
  130. django_cfg/modules/django_app_agent/agents/generation/app_generator/__init__.py +15 -0
  131. django_cfg/modules/django_app_agent/agents/generation/app_generator/config_validator.py +147 -0
  132. django_cfg/modules/django_app_agent/agents/generation/app_generator/main.py +99 -0
  133. django_cfg/modules/django_app_agent/agents/generation/app_generator/models.py +32 -0
  134. django_cfg/modules/django_app_agent/agents/generation/app_generator/prompt_manager.py +290 -0
  135. django_cfg/modules/django_app_agent/agents/interfaces.py +376 -0
  136. django_cfg/modules/django_app_agent/core/__init__.py +33 -0
  137. django_cfg/modules/django_app_agent/core/config.py +300 -0
  138. django_cfg/modules/django_app_agent/core/exceptions.py +359 -0
  139. django_cfg/modules/django_app_agent/models/__init__.py +71 -0
  140. django_cfg/modules/django_app_agent/models/base.py +283 -0
  141. django_cfg/modules/django_app_agent/models/context.py +496 -0
  142. django_cfg/modules/django_app_agent/models/enums.py +481 -0
  143. django_cfg/modules/django_app_agent/models/requests.py +500 -0
  144. django_cfg/modules/django_app_agent/models/responses.py +585 -0
  145. django_cfg/modules/django_app_agent/pytest.ini +6 -0
  146. django_cfg/modules/django_app_agent/services/__init__.py +42 -0
  147. django_cfg/modules/django_app_agent/services/app_generator/__init__.py +30 -0
  148. django_cfg/modules/django_app_agent/services/app_generator/ai_integration.py +133 -0
  149. django_cfg/modules/django_app_agent/services/app_generator/context.py +40 -0
  150. django_cfg/modules/django_app_agent/services/app_generator/main.py +202 -0
  151. django_cfg/modules/django_app_agent/services/app_generator/structure.py +316 -0
  152. django_cfg/modules/django_app_agent/services/app_generator/validation.py +125 -0
  153. django_cfg/modules/django_app_agent/services/base.py +437 -0
  154. django_cfg/modules/django_app_agent/services/context_builder/__init__.py +34 -0
  155. django_cfg/modules/django_app_agent/services/context_builder/code_extractor.py +141 -0
  156. django_cfg/modules/django_app_agent/services/context_builder/context_generator.py +276 -0
  157. django_cfg/modules/django_app_agent/services/context_builder/main.py +272 -0
  158. django_cfg/modules/django_app_agent/services/context_builder/models.py +40 -0
  159. django_cfg/modules/django_app_agent/services/context_builder/pattern_analyzer.py +85 -0
  160. django_cfg/modules/django_app_agent/services/project_scanner/__init__.py +31 -0
  161. django_cfg/modules/django_app_agent/services/project_scanner/app_discovery.py +311 -0
  162. django_cfg/modules/django_app_agent/services/project_scanner/main.py +221 -0
  163. django_cfg/modules/django_app_agent/services/project_scanner/models.py +59 -0
  164. django_cfg/modules/django_app_agent/services/project_scanner/pattern_detection.py +94 -0
  165. django_cfg/modules/django_app_agent/services/questioning_service/__init__.py +28 -0
  166. django_cfg/modules/django_app_agent/services/questioning_service/main.py +273 -0
  167. django_cfg/modules/django_app_agent/services/questioning_service/models.py +111 -0
  168. django_cfg/modules/django_app_agent/services/questioning_service/question_generator.py +251 -0
  169. django_cfg/modules/django_app_agent/services/questioning_service/response_processor.py +347 -0
  170. django_cfg/modules/django_app_agent/services/questioning_service/session_manager.py +356 -0
  171. django_cfg/modules/django_app_agent/services/report_service.py +332 -0
  172. django_cfg/modules/django_app_agent/services/template_manager/__init__.py +18 -0
  173. django_cfg/modules/django_app_agent/services/template_manager/jinja_engine.py +236 -0
  174. django_cfg/modules/django_app_agent/services/template_manager/main.py +159 -0
  175. django_cfg/modules/django_app_agent/services/template_manager/models.py +36 -0
  176. django_cfg/modules/django_app_agent/services/template_manager/template_loader.py +100 -0
  177. django_cfg/modules/django_app_agent/services/template_manager/templates/admin.py.j2 +105 -0
  178. django_cfg/modules/django_app_agent/services/template_manager/templates/apps.py.j2 +31 -0
  179. django_cfg/modules/django_app_agent/services/template_manager/templates/cfg_config.py.j2 +44 -0
  180. django_cfg/modules/django_app_agent/services/template_manager/templates/cfg_module.py.j2 +81 -0
  181. django_cfg/modules/django_app_agent/services/template_manager/templates/forms.py.j2 +107 -0
  182. django_cfg/modules/django_app_agent/services/template_manager/templates/models.py.j2 +139 -0
  183. django_cfg/modules/django_app_agent/services/template_manager/templates/serializers.py.j2 +91 -0
  184. django_cfg/modules/django_app_agent/services/template_manager/templates/tests.py.j2 +195 -0
  185. django_cfg/modules/django_app_agent/services/template_manager/templates/urls.py.j2 +35 -0
  186. django_cfg/modules/django_app_agent/services/template_manager/templates/views.py.j2 +211 -0
  187. django_cfg/modules/django_app_agent/services/template_manager/variable_processor.py +200 -0
  188. django_cfg/modules/django_app_agent/services/validation_service/__init__.py +25 -0
  189. django_cfg/modules/django_app_agent/services/validation_service/django_validator.py +333 -0
  190. django_cfg/modules/django_app_agent/services/validation_service/main.py +242 -0
  191. django_cfg/modules/django_app_agent/services/validation_service/models.py +66 -0
  192. django_cfg/modules/django_app_agent/services/validation_service/quality_validator.py +352 -0
  193. django_cfg/modules/django_app_agent/services/validation_service/security_validator.py +272 -0
  194. django_cfg/modules/django_app_agent/services/validation_service/syntax_validator.py +203 -0
  195. django_cfg/modules/django_app_agent/ui/__init__.py +25 -0
  196. django_cfg/modules/django_app_agent/ui/cli.py +419 -0
  197. django_cfg/modules/django_app_agent/ui/rich_components.py +622 -0
  198. django_cfg/modules/django_app_agent/utils/__init__.py +38 -0
  199. django_cfg/modules/django_app_agent/utils/logging.py +360 -0
  200. django_cfg/modules/django_app_agent/utils/validation.py +417 -0
  201. django_cfg/modules/django_currency/__init__.py +2 -2
  202. django_cfg/modules/django_currency/clients/__init__.py +2 -2
  203. django_cfg/modules/django_currency/clients/hybrid_client.py +587 -0
  204. django_cfg/modules/django_currency/core/converter.py +12 -12
  205. django_cfg/modules/django_currency/database/__init__.py +2 -2
  206. django_cfg/modules/django_currency/database/database_loader.py +93 -42
  207. django_cfg/modules/django_llm/llm/client.py +10 -2
  208. django_cfg/modules/django_unfold/callbacks/actions.py +1 -1
  209. django_cfg/modules/django_unfold/callbacks/statistics.py +1 -1
  210. django_cfg/modules/django_unfold/dashboard.py +14 -13
  211. django_cfg/modules/django_unfold/models/config.py +1 -1
  212. django_cfg/registry/core.py +3 -0
  213. django_cfg/registry/third_party.py +2 -2
  214. django_cfg/template_archive/django_sample.zip +0 -0
  215. {django_cfg-1.3.7.dist-info → django_cfg-1.3.9.dist-info}/METADATA +2 -1
  216. {django_cfg-1.3.7.dist-info → django_cfg-1.3.9.dist-info}/RECORD +223 -117
  217. django_cfg/apps/accounts/admin/activity.py +0 -96
  218. django_cfg/apps/accounts/admin/group.py +0 -17
  219. django_cfg/apps/accounts/admin/otp.py +0 -59
  220. django_cfg/apps/accounts/admin/registration_source.py +0 -97
  221. django_cfg/apps/accounts/admin/twilio_response.py +0 -227
  222. django_cfg/apps/accounts/admin/user.py +0 -300
  223. django_cfg/apps/agents/core/agent.py +0 -281
  224. django_cfg/apps/payments/admin_interface/old/payments/base.html +0 -175
  225. django_cfg/apps/payments/admin_interface/old/payments/components/dev_tool_card.html +0 -125
  226. django_cfg/apps/payments/admin_interface/old/payments/components/loading_spinner.html +0 -16
  227. django_cfg/apps/payments/admin_interface/old/payments/components/ngrok_status_card.html +0 -113
  228. django_cfg/apps/payments/admin_interface/old/payments/components/notification.html +0 -27
  229. django_cfg/apps/payments/admin_interface/old/payments/components/provider_card.html +0 -86
  230. django_cfg/apps/payments/admin_interface/old/payments/components/status_card.html +0 -35
  231. django_cfg/apps/payments/admin_interface/old/payments/currency_converter.html +0 -382
  232. django_cfg/apps/payments/admin_interface/old/payments/payment_dashboard.html +0 -309
  233. django_cfg/apps/payments/admin_interface/old/payments/payment_form.html +0 -303
  234. django_cfg/apps/payments/admin_interface/old/payments/payment_list.html +0 -382
  235. django_cfg/apps/payments/admin_interface/old/payments/payment_status.html +0 -500
  236. django_cfg/apps/payments/admin_interface/old/payments/webhook_dashboard.html +0 -518
  237. django_cfg/apps/payments/admin_interface/old/static/payments/css/components.css +0 -619
  238. django_cfg/apps/payments/admin_interface/old/static/payments/css/dashboard.css +0 -188
  239. django_cfg/apps/payments/admin_interface/old/static/payments/js/components.js +0 -545
  240. django_cfg/apps/payments/admin_interface/old/static/payments/js/ngrok-status.js +0 -163
  241. django_cfg/apps/payments/admin_interface/old/static/payments/js/utils.js +0 -412
  242. django_cfg/apps/tasks/admin.py +0 -320
  243. django_cfg/middleware/static_nocache.py +0 -55
  244. django_cfg/modules/django_currency/clients/yahoo_client.py +0 -157
  245. /django_cfg/modules/{django_unfold → django_admin}/icons/README.md +0 -0
  246. /django_cfg/modules/{django_unfold → django_admin}/icons/__init__.py +0 -0
  247. /django_cfg/modules/{django_unfold → django_admin}/icons/constants.py +0 -0
  248. /django_cfg/modules/{django_unfold → django_admin}/icons/generate_icons.py +0 -0
  249. {django_cfg-1.3.7.dist-info → django_cfg-1.3.9.dist-info}/WHEEL +0 -0
  250. {django_cfg-1.3.7.dist-info → django_cfg-1.3.9.dist-info}/entry_points.txt +0 -0
  251. {django_cfg-1.3.7.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, ProviderConfig
13
- from .nowpayments import NowPaymentsProvider, NowPaymentsConfig
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: NowPaymentsConfig,
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
- const token = document.querySelector('[name=csrfmiddlewaretoken]');
14
- return token ? token.value : '';
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/supported/`, { provider }),
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/webhooks/test/`, {
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) {