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.
Files changed (252) 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/apps/urls.py +1 -2
  97. django_cfg/config.py +1 -1
  98. django_cfg/core/config.py +10 -5
  99. django_cfg/core/generation.py +1 -1
  100. django_cfg/management/commands/__init__.py +13 -1
  101. django_cfg/management/commands/app_agent_diagnose.py +470 -0
  102. django_cfg/management/commands/app_agent_generate.py +342 -0
  103. django_cfg/management/commands/app_agent_info.py +308 -0
  104. django_cfg/management/commands/migrate_all.py +9 -3
  105. django_cfg/management/commands/migrator.py +11 -6
  106. django_cfg/management/commands/rundramatiq.py +3 -2
  107. django_cfg/middleware/__init__.py +0 -2
  108. django_cfg/models/api_keys.py +115 -0
  109. django_cfg/modules/django_admin/__init__.py +64 -0
  110. django_cfg/modules/django_admin/decorators/__init__.py +13 -0
  111. django_cfg/modules/django_admin/decorators/actions.py +106 -0
  112. django_cfg/modules/django_admin/decorators/display.py +106 -0
  113. django_cfg/modules/django_admin/mixins/__init__.py +14 -0
  114. django_cfg/modules/django_admin/mixins/display_mixin.py +81 -0
  115. django_cfg/modules/django_admin/mixins/optimization_mixin.py +41 -0
  116. django_cfg/modules/django_admin/mixins/standalone_actions_mixin.py +202 -0
  117. django_cfg/modules/django_admin/models/__init__.py +20 -0
  118. django_cfg/modules/django_admin/models/action_models.py +33 -0
  119. django_cfg/modules/django_admin/models/badge_models.py +20 -0
  120. django_cfg/modules/django_admin/models/base.py +26 -0
  121. django_cfg/modules/django_admin/models/display_models.py +31 -0
  122. django_cfg/modules/django_admin/utils/badges.py +159 -0
  123. django_cfg/modules/django_admin/utils/displays.py +247 -0
  124. django_cfg/modules/django_app_agent/__init__.py +87 -0
  125. django_cfg/modules/django_app_agent/agents/__init__.py +40 -0
  126. django_cfg/modules/django_app_agent/agents/base/__init__.py +24 -0
  127. django_cfg/modules/django_app_agent/agents/base/agent.py +354 -0
  128. django_cfg/modules/django_app_agent/agents/base/context.py +236 -0
  129. django_cfg/modules/django_app_agent/agents/base/executor.py +430 -0
  130. django_cfg/modules/django_app_agent/agents/generation/__init__.py +12 -0
  131. django_cfg/modules/django_app_agent/agents/generation/app_generator/__init__.py +15 -0
  132. django_cfg/modules/django_app_agent/agents/generation/app_generator/config_validator.py +147 -0
  133. django_cfg/modules/django_app_agent/agents/generation/app_generator/main.py +99 -0
  134. django_cfg/modules/django_app_agent/agents/generation/app_generator/models.py +32 -0
  135. django_cfg/modules/django_app_agent/agents/generation/app_generator/prompt_manager.py +290 -0
  136. django_cfg/modules/django_app_agent/agents/interfaces.py +376 -0
  137. django_cfg/modules/django_app_agent/core/__init__.py +33 -0
  138. django_cfg/modules/django_app_agent/core/config.py +300 -0
  139. django_cfg/modules/django_app_agent/core/exceptions.py +359 -0
  140. django_cfg/modules/django_app_agent/models/__init__.py +71 -0
  141. django_cfg/modules/django_app_agent/models/base.py +283 -0
  142. django_cfg/modules/django_app_agent/models/context.py +496 -0
  143. django_cfg/modules/django_app_agent/models/enums.py +481 -0
  144. django_cfg/modules/django_app_agent/models/requests.py +500 -0
  145. django_cfg/modules/django_app_agent/models/responses.py +585 -0
  146. django_cfg/modules/django_app_agent/pytest.ini +6 -0
  147. django_cfg/modules/django_app_agent/services/__init__.py +42 -0
  148. django_cfg/modules/django_app_agent/services/app_generator/__init__.py +30 -0
  149. django_cfg/modules/django_app_agent/services/app_generator/ai_integration.py +133 -0
  150. django_cfg/modules/django_app_agent/services/app_generator/context.py +40 -0
  151. django_cfg/modules/django_app_agent/services/app_generator/main.py +202 -0
  152. django_cfg/modules/django_app_agent/services/app_generator/structure.py +316 -0
  153. django_cfg/modules/django_app_agent/services/app_generator/validation.py +125 -0
  154. django_cfg/modules/django_app_agent/services/base.py +437 -0
  155. django_cfg/modules/django_app_agent/services/context_builder/__init__.py +34 -0
  156. django_cfg/modules/django_app_agent/services/context_builder/code_extractor.py +141 -0
  157. django_cfg/modules/django_app_agent/services/context_builder/context_generator.py +276 -0
  158. django_cfg/modules/django_app_agent/services/context_builder/main.py +272 -0
  159. django_cfg/modules/django_app_agent/services/context_builder/models.py +40 -0
  160. django_cfg/modules/django_app_agent/services/context_builder/pattern_analyzer.py +85 -0
  161. django_cfg/modules/django_app_agent/services/project_scanner/__init__.py +31 -0
  162. django_cfg/modules/django_app_agent/services/project_scanner/app_discovery.py +311 -0
  163. django_cfg/modules/django_app_agent/services/project_scanner/main.py +221 -0
  164. django_cfg/modules/django_app_agent/services/project_scanner/models.py +59 -0
  165. django_cfg/modules/django_app_agent/services/project_scanner/pattern_detection.py +94 -0
  166. django_cfg/modules/django_app_agent/services/questioning_service/__init__.py +28 -0
  167. django_cfg/modules/django_app_agent/services/questioning_service/main.py +273 -0
  168. django_cfg/modules/django_app_agent/services/questioning_service/models.py +111 -0
  169. django_cfg/modules/django_app_agent/services/questioning_service/question_generator.py +251 -0
  170. django_cfg/modules/django_app_agent/services/questioning_service/response_processor.py +347 -0
  171. django_cfg/modules/django_app_agent/services/questioning_service/session_manager.py +356 -0
  172. django_cfg/modules/django_app_agent/services/report_service.py +332 -0
  173. django_cfg/modules/django_app_agent/services/template_manager/__init__.py +18 -0
  174. django_cfg/modules/django_app_agent/services/template_manager/jinja_engine.py +236 -0
  175. django_cfg/modules/django_app_agent/services/template_manager/main.py +159 -0
  176. django_cfg/modules/django_app_agent/services/template_manager/models.py +36 -0
  177. django_cfg/modules/django_app_agent/services/template_manager/template_loader.py +100 -0
  178. django_cfg/modules/django_app_agent/services/template_manager/templates/admin.py.j2 +105 -0
  179. django_cfg/modules/django_app_agent/services/template_manager/templates/apps.py.j2 +31 -0
  180. django_cfg/modules/django_app_agent/services/template_manager/templates/cfg_config.py.j2 +44 -0
  181. django_cfg/modules/django_app_agent/services/template_manager/templates/cfg_module.py.j2 +81 -0
  182. django_cfg/modules/django_app_agent/services/template_manager/templates/forms.py.j2 +107 -0
  183. django_cfg/modules/django_app_agent/services/template_manager/templates/models.py.j2 +139 -0
  184. django_cfg/modules/django_app_agent/services/template_manager/templates/serializers.py.j2 +91 -0
  185. django_cfg/modules/django_app_agent/services/template_manager/templates/tests.py.j2 +195 -0
  186. django_cfg/modules/django_app_agent/services/template_manager/templates/urls.py.j2 +35 -0
  187. django_cfg/modules/django_app_agent/services/template_manager/templates/views.py.j2 +211 -0
  188. django_cfg/modules/django_app_agent/services/template_manager/variable_processor.py +200 -0
  189. django_cfg/modules/django_app_agent/services/validation_service/__init__.py +25 -0
  190. django_cfg/modules/django_app_agent/services/validation_service/django_validator.py +333 -0
  191. django_cfg/modules/django_app_agent/services/validation_service/main.py +242 -0
  192. django_cfg/modules/django_app_agent/services/validation_service/models.py +66 -0
  193. django_cfg/modules/django_app_agent/services/validation_service/quality_validator.py +352 -0
  194. django_cfg/modules/django_app_agent/services/validation_service/security_validator.py +272 -0
  195. django_cfg/modules/django_app_agent/services/validation_service/syntax_validator.py +203 -0
  196. django_cfg/modules/django_app_agent/ui/__init__.py +25 -0
  197. django_cfg/modules/django_app_agent/ui/cli.py +419 -0
  198. django_cfg/modules/django_app_agent/ui/rich_components.py +622 -0
  199. django_cfg/modules/django_app_agent/utils/__init__.py +38 -0
  200. django_cfg/modules/django_app_agent/utils/logging.py +360 -0
  201. django_cfg/modules/django_app_agent/utils/validation.py +417 -0
  202. django_cfg/modules/django_currency/__init__.py +2 -2
  203. django_cfg/modules/django_currency/clients/__init__.py +2 -2
  204. django_cfg/modules/django_currency/clients/hybrid_client.py +587 -0
  205. django_cfg/modules/django_currency/core/converter.py +12 -12
  206. django_cfg/modules/django_currency/database/__init__.py +2 -2
  207. django_cfg/modules/django_currency/database/database_loader.py +93 -42
  208. django_cfg/modules/django_llm/llm/client.py +10 -2
  209. django_cfg/modules/django_unfold/callbacks/actions.py +1 -1
  210. django_cfg/modules/django_unfold/callbacks/statistics.py +1 -1
  211. django_cfg/modules/django_unfold/dashboard.py +14 -13
  212. django_cfg/modules/django_unfold/models/config.py +1 -1
  213. django_cfg/registry/core.py +3 -0
  214. django_cfg/registry/third_party.py +2 -2
  215. django_cfg/template_archive/django_sample.zip +0 -0
  216. {django_cfg-1.3.5.dist-info → django_cfg-1.3.9.dist-info}/METADATA +2 -1
  217. {django_cfg-1.3.5.dist-info → django_cfg-1.3.9.dist-info}/RECORD +224 -118
  218. django_cfg/apps/accounts/admin/activity.py +0 -96
  219. django_cfg/apps/accounts/admin/group.py +0 -17
  220. django_cfg/apps/accounts/admin/otp.py +0 -59
  221. django_cfg/apps/accounts/admin/registration_source.py +0 -97
  222. django_cfg/apps/accounts/admin/twilio_response.py +0 -227
  223. django_cfg/apps/accounts/admin/user.py +0 -300
  224. django_cfg/apps/agents/core/agent.py +0 -281
  225. django_cfg/apps/payments/admin_interface/old/payments/base.html +0 -175
  226. django_cfg/apps/payments/admin_interface/old/payments/components/dev_tool_card.html +0 -125
  227. django_cfg/apps/payments/admin_interface/old/payments/components/loading_spinner.html +0 -16
  228. django_cfg/apps/payments/admin_interface/old/payments/components/ngrok_status_card.html +0 -113
  229. django_cfg/apps/payments/admin_interface/old/payments/components/notification.html +0 -27
  230. django_cfg/apps/payments/admin_interface/old/payments/components/provider_card.html +0 -86
  231. django_cfg/apps/payments/admin_interface/old/payments/components/status_card.html +0 -35
  232. django_cfg/apps/payments/admin_interface/old/payments/currency_converter.html +0 -382
  233. django_cfg/apps/payments/admin_interface/old/payments/payment_dashboard.html +0 -309
  234. django_cfg/apps/payments/admin_interface/old/payments/payment_form.html +0 -303
  235. django_cfg/apps/payments/admin_interface/old/payments/payment_list.html +0 -382
  236. django_cfg/apps/payments/admin_interface/old/payments/payment_status.html +0 -500
  237. django_cfg/apps/payments/admin_interface/old/payments/webhook_dashboard.html +0 -518
  238. django_cfg/apps/payments/admin_interface/old/static/payments/css/components.css +0 -619
  239. django_cfg/apps/payments/admin_interface/old/static/payments/css/dashboard.css +0 -188
  240. django_cfg/apps/payments/admin_interface/old/static/payments/js/components.js +0 -545
  241. django_cfg/apps/payments/admin_interface/old/static/payments/js/ngrok-status.js +0 -163
  242. django_cfg/apps/payments/admin_interface/old/static/payments/js/utils.js +0 -412
  243. django_cfg/apps/tasks/admin.py +0 -320
  244. django_cfg/middleware/static_nocache.py +0 -55
  245. django_cfg/modules/django_currency/clients/yahoo_client.py +0 -157
  246. /django_cfg/modules/{django_unfold → django_admin}/icons/README.md +0 -0
  247. /django_cfg/modules/{django_unfold → django_admin}/icons/__init__.py +0 -0
  248. /django_cfg/modules/{django_unfold → django_admin}/icons/constants.py +0 -0
  249. /django_cfg/modules/{django_unfold → django_admin}/icons/generate_icons.py +0 -0
  250. {django_cfg-1.3.5.dist-info → django_cfg-1.3.9.dist-info}/WHEEL +0 -0
  251. {django_cfg-1.3.5.dist-info → django_cfg-1.3.9.dist-info}/entry_points.txt +0 -0
  252. {django_cfg-1.3.5.dist-info → django_cfg-1.3.9.dist-info}/licenses/LICENSE +0 -0
@@ -16,6 +16,10 @@ from django.contrib.auth import get_user_model
16
16
  from ..models import APIKey, Subscription
17
17
  from ..config.helpers import MiddlewareConfigHelper
18
18
  from django_cfg.modules.django_logger import get_logger
19
+ from ..tasks.usage_tracking import (
20
+ update_api_key_usage_async,
21
+ update_subscription_usage_async
22
+ )
19
23
 
20
24
  User = get_user_model()
21
25
  logger = get_logger("api_access_middleware")
@@ -335,25 +339,47 @@ class APIAccessMiddleware(MiddlewareMixin):
335
339
 
336
340
  def _track_usage_async(self, api_key: APIKey, request: HttpRequest):
337
341
  """
338
- Track API usage asynchronously to avoid blocking the request.
342
+ Track API usage asynchronously using background tasks.
343
+
344
+ This method replaces the blocking database writes with async task queuing,
345
+ dramatically improving response times and reducing database load.
339
346
  """
340
347
  try:
341
- # Increment usage counter (this will trigger signals)
342
- api_key.increment_usage(ip_address=self._get_client_ip(request))
348
+ # Send API key usage update to background queue (non-blocking)
349
+ update_api_key_usage_async.send(
350
+ api_key_id=str(api_key.id),
351
+ ip_address=self._get_client_ip(request)
352
+ )
353
+
354
+ # If user has active subscription, update subscription usage
355
+ if hasattr(request, 'subscription_access') and request.subscription_access.get('allowed'):
356
+ subscription_id = request.subscription_access.get('subscription_id')
357
+ if subscription_id:
358
+ update_subscription_usage_async.send(
359
+ subscription_id=subscription_id
360
+ )
343
361
 
344
- # Update usage analytics in cache
362
+ # Update lightweight analytics in cache (fast operations only)
345
363
  today = timezone.now().date().isoformat()
346
364
 
347
- # Daily usage counter
365
+ # Daily usage counter (for quick dashboard access)
348
366
  daily_key = f"api_usage_daily:{api_key.user.id}:{today}"
349
367
  cache.set(daily_key, cache.get(daily_key, 0) + 1, timeout=86400 * 2)
350
368
 
351
- # Endpoint usage counter
369
+ # Endpoint usage counter (for analytics)
352
370
  endpoint_key = f"endpoint_usage:{request.path}:{today}"
353
371
  cache.set(endpoint_key, cache.get(endpoint_key, 0) + 1, timeout=86400 * 2)
354
372
 
373
+ logger.debug(f"Usage tracking queued", extra={
374
+ 'api_key_id': str(api_key.id),
375
+ 'user_id': api_key.user.id,
376
+ 'path': request.path,
377
+ 'method': 'background_tasks'
378
+ })
379
+
355
380
  except Exception as e:
356
381
  logger.warning(f"Usage tracking failed: {e}")
382
+ # Don't fail the request if usage tracking fails
357
383
 
358
384
  def _get_client_ip(self, request: HttpRequest) -> str:
359
385
  """
@@ -0,0 +1,26 @@
1
+ # Generated by Django 5.2.6 on 2025-09-27 12:59
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+ dependencies = [
8
+ ("payments", "0001_initial"),
9
+ ]
10
+
11
+ operations = [
12
+ migrations.AddField(
13
+ model_name="currency",
14
+ name="usd_rate",
15
+ field=models.FloatField(
16
+ default=1.0, help_text="Current USD exchange rate (1 unit = X USD)"
17
+ ),
18
+ ),
19
+ migrations.AddField(
20
+ model_name="currency",
21
+ name="usd_rate_updated_at",
22
+ field=models.DateTimeField(
23
+ blank=True, help_text="When USD rate was last updated", null=True
24
+ ),
25
+ ),
26
+ ]
@@ -0,0 +1,28 @@
1
+ # Generated by Django 5.2.6 on 2025-09-27 16:46
2
+
3
+ from django.db import migrations
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+ dependencies = [
8
+ ("payments", "0002_currency_usd_rate_currency_usd_rate_updated_at"),
9
+ ]
10
+
11
+ operations = [
12
+ migrations.RemoveField(
13
+ model_name="providercurrency",
14
+ name="fee_percentage",
15
+ ),
16
+ migrations.RemoveField(
17
+ model_name="providercurrency",
18
+ name="fixed_fee",
19
+ ),
20
+ migrations.RemoveField(
21
+ model_name="providercurrency",
22
+ name="max_amount",
23
+ ),
24
+ migrations.RemoveField(
25
+ model_name="providercurrency",
26
+ name="min_amount",
27
+ ),
28
+ ]
@@ -0,0 +1,30 @@
1
+ # Generated by Django 5.2.6 on 2025-09-27 18:16
2
+
3
+ import django.core.validators
4
+ from django.conf import settings
5
+ from django.db import migrations, models
6
+
7
+
8
+ class Migration(migrations.Migration):
9
+ dependencies = [
10
+ ("payments", "0003_remove_provider_currency_fields"),
11
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
12
+ ]
13
+
14
+ operations = [
15
+ migrations.AddField(
16
+ model_name="userbalance",
17
+ name="reserved_usd",
18
+ field=models.FloatField(
19
+ default=0.0,
20
+ help_text="Reserved amount in USD (pending transactions)",
21
+ validators=[django.core.validators.MinValueValidator(0.0)],
22
+ ),
23
+ ),
24
+ migrations.AddConstraint(
25
+ model_name="userbalance",
26
+ constraint=models.CheckConstraint(
27
+ condition=models.Q(("reserved_usd__gte", 0.0)), name="reserved_non_negative_check"
28
+ ),
29
+ ),
30
+ ]
@@ -35,6 +35,12 @@ class UserBalance(models.Model):
35
35
  help_text="Current balance in USD (float for performance)"
36
36
  )
37
37
 
38
+ reserved_usd = models.FloatField(
39
+ default=0.0,
40
+ validators=[MinValueValidator(0.0)],
41
+ help_text="Reserved amount in USD (pending transactions)"
42
+ )
43
+
38
44
  # Tracking fields
39
45
  total_deposited = models.FloatField(
40
46
  default=0.0,
@@ -75,6 +81,10 @@ class UserBalance(models.Model):
75
81
  check=models.Q(balance_usd__gte=0.0),
76
82
  name='balance_non_negative_check'
77
83
  ),
84
+ models.CheckConstraint(
85
+ check=models.Q(reserved_usd__gte=0.0),
86
+ name='reserved_non_negative_check'
87
+ ),
78
88
  ]
79
89
 
80
90
  def __str__(self):
@@ -84,6 +94,8 @@ class UserBalance(models.Model):
84
94
  """Validate balance data."""
85
95
  if self.balance_usd < 0:
86
96
  raise ValidationError("Balance cannot be negative")
97
+ if self.reserved_usd < 0:
98
+ raise ValidationError("Reserved amount cannot be negative")
87
99
 
88
100
  @property
89
101
  def balance_display(self) -> str:
@@ -62,6 +62,17 @@ class Currency(TimestampedModel):
62
62
  help_text="Source for exchange rates (auto-detected by django_currency)"
63
63
  )
64
64
 
65
+ usd_rate = models.FloatField(
66
+ default=1.0,
67
+ help_text="Current USD exchange rate (1 unit = X USD)"
68
+ )
69
+
70
+ usd_rate_updated_at = models.DateTimeField(
71
+ null=True,
72
+ blank=True,
73
+ help_text="When USD rate was last updated"
74
+ )
75
+
65
76
  # Manager
66
77
  from .managers.currency_managers import CurrencyManager
67
78
  objects = CurrencyManager()
@@ -100,6 +111,19 @@ class Currency(TimestampedModel):
100
111
  def is_fiat(self) -> bool:
101
112
  """Check if this is a fiat currency."""
102
113
  return self.currency_type == self.CurrencyType.FIAT
114
+
115
+ def get_provider_config(self, provider: str):
116
+ """
117
+ Get provider configuration for this currency.
118
+
119
+ Args:
120
+ provider: Provider name (e.g., 'nowpayments')
121
+
122
+ Returns:
123
+ dict: Provider configuration or None
124
+ """
125
+ return Currency.objects.get_provider_config(self.code, provider)
126
+
103
127
 
104
128
 
105
129
  class Network(TimestampedModel):
@@ -206,41 +230,14 @@ class ProviderCurrency(TimestampedModel):
206
230
  help_text="Currency code as used by the provider"
207
231
  )
208
232
 
209
- min_amount = models.DecimalField(
210
- max_digits=20,
211
- decimal_places=8,
212
- null=True,
213
- blank=True,
214
- help_text="Minimum payment amount for this currency"
215
- )
216
-
217
- max_amount = models.DecimalField(
218
- max_digits=20,
219
- decimal_places=8,
220
- null=True,
221
- blank=True,
222
- help_text="Maximum payment amount for this currency"
223
- )
233
+ # min_amount and max_amount removed - now provided by provider configuration properties
224
234
 
225
235
  is_enabled = models.BooleanField(
226
236
  default=True,
227
237
  help_text="Whether this currency is enabled for this provider"
228
238
  )
229
239
 
230
- # Fee configuration
231
- fee_percentage = models.DecimalField(
232
- max_digits=5,
233
- decimal_places=4,
234
- default=0,
235
- help_text="Fee percentage (0.0250 = 2.5%)"
236
- )
237
-
238
- fixed_fee = models.DecimalField(
239
- max_digits=20,
240
- decimal_places=8,
241
- default=0,
242
- help_text="Fixed fee amount in this currency"
243
- )
240
+ # Fee configuration removed - now provided by provider configuration properties
244
241
 
245
242
  class Meta:
246
243
  db_table = 'payments_provider_currencies'
@@ -267,12 +264,89 @@ class ProviderCurrency(TimestampedModel):
267
264
  if self.currency and self.currency.is_fiat and self.network:
268
265
  raise ValidationError("Fiat currencies should not specify a network")
269
266
 
270
- # Validate amount limits
271
- if self.min_amount and self.max_amount and self.min_amount > self.max_amount:
272
- raise ValidationError("Minimum amount cannot be greater than maximum amount")
267
+ # Amount limits validation removed - now handled by provider configuration
273
268
 
274
269
  @property
275
270
  def display_name(self) -> str:
276
271
  """Human-readable display name."""
277
272
  network_part = f" on {self.network.name}" if self.network else ""
278
273
  return f"{self.currency.name}{network_part}"
274
+
275
+ # Provider configuration properties (get values from provider config)
276
+ @property
277
+ def provider_fee_percentage(self) -> float:
278
+ """Get fee percentage from provider configuration."""
279
+ try:
280
+ from ..services.providers.registry import get_provider_registry
281
+ registry = get_provider_registry()
282
+ provider_instance = registry.get_provider(self.provider)
283
+ if provider_instance:
284
+ return float(provider_instance.get_fee_percentage(self.currency.code, self.currency.currency_type))
285
+ return 0.005 # Default 0.5%
286
+ except Exception:
287
+ return 0.005 # Default fallback
288
+
289
+ @property
290
+ def provider_fixed_fee_usd(self) -> float:
291
+ """Get fixed fee from provider configuration."""
292
+ try:
293
+ from ..services.providers.registry import get_provider_registry
294
+ registry = get_provider_registry()
295
+ provider_instance = registry.get_provider(self.provider)
296
+ if provider_instance:
297
+ return float(provider_instance.get_fixed_fee_usd(self.currency.code, self.currency.currency_type))
298
+ return 0.0 # Default no fee
299
+ except Exception:
300
+ return 0.0 # Default fallback
301
+
302
+ @property
303
+ def provider_min_amount_usd(self) -> float:
304
+ """Get minimum amount from provider configuration."""
305
+ try:
306
+ from ..services.providers.registry import get_provider_registry
307
+ registry = get_provider_registry()
308
+ provider_instance = registry.get_provider(self.provider)
309
+ if provider_instance:
310
+ return float(provider_instance.get_min_amount_usd(self.currency.code, self.currency.currency_type))
311
+ return 0.000001 # Default minimum
312
+ except Exception:
313
+ return 0.000001 # Default fallback
314
+
315
+ @property
316
+ def provider_max_amount_usd(self) -> float:
317
+ """Get maximum amount from provider configuration."""
318
+ try:
319
+ from ..services.providers.registry import get_provider_registry
320
+ registry = get_provider_registry()
321
+ provider_instance = registry.get_provider(self.provider)
322
+ if provider_instance:
323
+ return float(provider_instance.get_max_amount_usd(self.currency.code, self.currency.currency_type))
324
+ return 1000000.0 # Default maximum
325
+ except Exception:
326
+ return 1000000.0 # Default fallback
327
+
328
+ @property
329
+ def provider_confirmation_blocks(self) -> int:
330
+ """Get confirmation blocks from provider configuration."""
331
+ try:
332
+ from ..services.providers.registry import get_provider_registry
333
+ registry = get_provider_registry()
334
+ provider_instance = registry.get_provider(self.provider)
335
+ if provider_instance and self.network:
336
+ return provider_instance.get_confirmation_blocks(self.network.code)
337
+ return 1 # Default
338
+ except Exception:
339
+ return 1 # Default fallback
340
+
341
+ @property
342
+ def provider_network_name(self) -> str:
343
+ """Get network name from provider configuration."""
344
+ try:
345
+ from ..services.providers.registry import get_provider_registry
346
+ registry = get_provider_registry()
347
+ provider_instance = registry.get_provider(self.provider)
348
+ if provider_instance and self.network:
349
+ return provider_instance.get_network_name(self.network.code)
350
+ return self.network.name if self.network else 'Unknown'
351
+ except Exception:
352
+ return self.network.name if self.network else 'Unknown'
@@ -383,3 +383,68 @@ class CurrencyManager(models.Manager):
383
383
  stats['by_provider'][provider] = queryset.supported_by_provider(provider).count()
384
384
 
385
385
  return stats
386
+
387
+ def get_provider_config(self, currency_code: str, provider: str):
388
+ """
389
+ Get provider configuration for currency.
390
+
391
+ Args:
392
+ currency_code: Currency code (e.g., 'BTC')
393
+ provider: Provider name (e.g., 'nowpayments')
394
+
395
+ Returns:
396
+ dict: Provider configuration or None
397
+ """
398
+ try:
399
+ from ..currencies import ProviderCurrency
400
+ from ...services.providers.registry import get_provider_registry
401
+
402
+ # Get provider instance
403
+ registry = get_provider_registry()
404
+ provider_instance = registry.get_provider(provider)
405
+
406
+ if not provider_instance:
407
+ return None
408
+
409
+ # Get currency
410
+ currency = self.by_code(currency_code)
411
+ if not currency:
412
+ return None
413
+
414
+ # Get provider currency config from DB
415
+ try:
416
+ provider_currency = ProviderCurrency.objects.get(
417
+ currency=currency,
418
+ provider=provider,
419
+ is_enabled=True
420
+ )
421
+
422
+ # Get configuration from provider
423
+ return {
424
+ 'fee_percentage': float(provider_instance.get_fee_percentage(currency_code, currency.currency_type)),
425
+ 'fixed_fee_usd': float(provider_instance.get_fixed_fee_usd(currency_code, currency.currency_type)),
426
+ 'min_amount_usd': float(provider_instance.get_min_amount_usd(currency_code, currency.currency_type)),
427
+ 'max_amount_usd': float(provider_instance.get_max_amount_usd(currency_code, currency.currency_type)),
428
+ 'confirmation_blocks': provider_instance.get_confirmation_blocks(provider_currency.network.code if provider_currency.network else ''),
429
+ 'network_name': provider_instance.get_network_name(provider_currency.network.code if provider_currency.network else ''),
430
+ 'network_code': provider_currency.network.code if provider_currency.network else None,
431
+ }
432
+
433
+ except ProviderCurrency.DoesNotExist:
434
+ # Return default configuration from provider
435
+ return {
436
+ 'fee_percentage': float(provider_instance.get_fee_percentage(currency_code, currency.currency_type)),
437
+ 'fixed_fee_usd': float(provider_instance.get_fixed_fee_usd(currency_code, currency.currency_type)),
438
+ 'min_amount_usd': float(provider_instance.get_min_amount_usd(currency_code, currency.currency_type)),
439
+ 'max_amount_usd': float(provider_instance.get_max_amount_usd(currency_code, currency.currency_type)),
440
+ 'confirmation_blocks': 1,
441
+ 'network_name': 'Unknown',
442
+ 'network_code': None,
443
+ }
444
+
445
+ except Exception as e:
446
+ logger.error(f"Failed to get provider config: {e}", extra={
447
+ 'currency_code': currency_code,
448
+ 'provider': provider
449
+ })
450
+ return None
@@ -4,10 +4,9 @@ Currency service for the Universal Payment System v2.0.
4
4
  Handles currency conversion and rate management using django_currency module.
5
5
  """
6
6
 
7
- from typing import Optional, Dict, Any, List
8
- from decimal import Decimal
7
+ from typing import Optional, List
9
8
  from django.utils import timezone
10
- from datetime import timedelta
9
+ from django.db import models
11
10
 
12
11
  from .base import BaseService
13
12
  from ..types import (
@@ -161,29 +160,35 @@ class CurrencyService(BaseService):
161
160
  quote_currency=quote_currency
162
161
  )
163
162
 
164
- def get_supported_currencies(self, provider: Optional[str] = None) -> ServiceOperationResult:
163
+ def get_supported_currencies(self, provider: Optional[str] = None, currency_type: str = 'all') -> ServiceOperationResult:
165
164
  """
166
165
  Get list of supported currencies.
167
166
 
168
167
  Args:
169
168
  provider: Filter by provider (optional)
169
+ currency_type: Filter by currency type ('all', 'crypto', 'fiat')
170
170
 
171
171
  Returns:
172
172
  ServiceOperationResult: List of supported currencies
173
173
  """
174
174
  try:
175
175
  self.logger.debug("Getting supported currencies", extra={
176
- 'provider': provider
176
+ 'provider': provider,
177
+ 'currency_type': currency_type
177
178
  })
178
179
 
179
180
  # Get currencies from database
180
- queryset = Currency.objects.filter(is_enabled=True)
181
+ queryset = Currency.objects.filter(is_active=True)
182
+
183
+ # Filter by currency type
184
+ if currency_type and currency_type != 'all':
185
+ queryset = queryset.filter(currency_type=currency_type)
181
186
 
182
187
  if provider:
183
188
  # Filter by provider support
184
189
  queryset = queryset.filter(
185
- providercurrency__provider=provider,
186
- providercurrency__is_enabled=True
190
+ provider_configs__provider=provider,
191
+ provider_configs__is_enabled=True
187
192
  ).distinct()
188
193
 
189
194
  currencies = queryset.order_by('code')
@@ -197,18 +202,20 @@ class CurrencyService(BaseService):
197
202
  # Add provider-specific info if requested
198
203
  if provider:
199
204
  try:
200
- provider_currency = ProviderCurrency.objects.get(
205
+ provider_currency = ProviderCurrency.objects.filter(
201
206
  currency=currency,
202
207
  provider=provider,
203
208
  is_enabled=True
204
- )
205
- currency_info['provider_info'] = {
206
- 'min_amount': float(provider_currency.min_amount) if provider_currency.min_amount else None,
207
- 'max_amount': float(provider_currency.max_amount) if provider_currency.max_amount else None,
208
- 'network_fee': float(provider_currency.network_fee) if provider_currency.network_fee else None,
209
- 'confirmation_blocks': provider_currency.confirmation_blocks
210
- }
211
- except ProviderCurrency.DoesNotExist:
209
+ ).first()
210
+ if provider_currency:
211
+ currency_info['provider_info'] = {
212
+ 'min_amount': provider_currency.provider_min_amount_usd,
213
+ 'max_amount': provider_currency.provider_max_amount_usd,
214
+ 'fee_percentage': provider_currency.provider_fee_percentage,
215
+ 'fixed_fee_usd': provider_currency.provider_fixed_fee_usd,
216
+ 'confirmation_blocks': provider_currency.provider_confirmation_blocks
217
+ }
218
+ except Exception:
212
219
  pass
213
220
 
214
221
  currency_data.append(currency_info)
@@ -245,7 +252,7 @@ class CurrencyService(BaseService):
245
252
 
246
253
  # Get currency
247
254
  try:
248
- currency = Currency.objects.get(code=currency_code, is_enabled=True)
255
+ currency = Currency.objects.get(code=currency_code, is_active=True)
249
256
  except Currency.DoesNotExist:
250
257
  return self._create_error_result(
251
258
  f"Currency {currency_code} not found or disabled",
@@ -255,7 +262,7 @@ class CurrencyService(BaseService):
255
262
  # Get networks
256
263
  networks = Network.objects.filter(
257
264
  currency_code=currency_code,
258
- is_enabled=True
265
+ is_active=True
259
266
  ).order_by('name')
260
267
 
261
268
  network_data = []
@@ -305,10 +312,10 @@ class CurrencyService(BaseService):
305
312
  if currency_codes:
306
313
  currencies = Currency.objects.filter(
307
314
  code__in=currency_codes,
308
- is_enabled=True
315
+ is_active=True
309
316
  )
310
317
  else:
311
- currencies = Currency.objects.filter(is_enabled=True)
318
+ currencies = Currency.objects.filter(is_active=True)
312
319
 
313
320
  updated_count = 0
314
321
  failed_count = 0
@@ -369,14 +376,14 @@ class CurrencyService(BaseService):
369
376
  try:
370
377
  # Currency counts
371
378
  total_currencies = Currency.objects.count()
372
- enabled_currencies = Currency.objects.filter(is_enabled=True).count()
379
+ enabled_currencies = Currency.objects.filter(is_active=True).count()
373
380
  crypto_currencies = Currency.objects.filter(
374
381
  currency_type=Currency.CurrencyType.CRYPTO,
375
- is_enabled=True
382
+ is_active=True
376
383
  ).count()
377
384
  fiat_currencies = Currency.objects.filter(
378
385
  currency_type=Currency.CurrencyType.FIAT,
379
- is_enabled=True
386
+ is_active=True
380
387
  ).count()
381
388
 
382
389
  # Provider support
@@ -388,7 +395,7 @@ class CurrencyService(BaseService):
388
395
 
389
396
  # Network stats
390
397
  network_stats = Network.objects.filter(
391
- is_enabled=True
398
+ is_active=True
392
399
  ).values('currency_code').annotate(
393
400
  network_count=models.Count('id')
394
401
  ).order_by('-network_count')
@@ -415,14 +422,14 @@ class CurrencyService(BaseService):
415
422
  """Validate that currencies are supported."""
416
423
  try:
417
424
  # Check from_currency
418
- if not Currency.objects.filter(code=from_currency, is_enabled=True).exists():
425
+ if not Currency.objects.filter(code=from_currency, is_active=True).exists():
419
426
  return self._create_error_result(
420
427
  f"Currency {from_currency} not supported",
421
428
  "from_currency_not_supported"
422
429
  )
423
430
 
424
431
  # Check to_currency
425
- if not Currency.objects.filter(code=to_currency, is_enabled=True).exists():
432
+ if not Currency.objects.filter(code=to_currency, is_active=True).exists():
426
433
  return self._create_error_result(
427
434
  f"Currency {to_currency} not supported",
428
435
  "to_currency_not_supported"
@@ -440,7 +447,7 @@ class CurrencyService(BaseService):
440
447
  """Perform currency service health check."""
441
448
  try:
442
449
  # Check database connectivity
443
- currency_count = Currency.objects.filter(is_enabled=True).count()
450
+ currency_count = Currency.objects.filter(is_active=True).count()
444
451
 
445
452
  # Test currency conversion
446
453
  try:
@@ -104,7 +104,7 @@ class PaymentService(BaseService):
104
104
  payment = self._execute_with_transaction(create_payment_transaction)
105
105
 
106
106
  # Create payment with provider
107
- from ..providers.base import PaymentRequest as ProviderPaymentRequest
107
+ from ..providers.models import PaymentRequest as ProviderPaymentRequest
108
108
 
109
109
  provider_request = ProviderPaymentRequest(
110
110
  amount_usd=request.amount_usd,
@@ -7,6 +7,7 @@ Provider implementations with unified interface and Pydantic validation.
7
7
  from .base import BaseProvider
8
8
  from .nowpayments import NowPaymentsProvider
9
9
  from .registry import ProviderRegistry, get_provider_registry, initialize_providers
10
+ from .sync_service import ProviderSyncService, get_provider_sync_service
10
11
 
11
12
  __all__ = [
12
13
  'BaseProvider',
@@ -14,4 +15,6 @@ __all__ = [
14
15
  'ProviderRegistry',
15
16
  'get_provider_registry',
16
17
  'initialize_providers',
18
+ 'ProviderSyncService',
19
+ 'get_provider_sync_service',
17
20
  ]