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
@@ -24,10 +24,15 @@ function paymentForm() {
24
24
  users: [],
25
25
 
26
26
  async init() {
27
+ console.log('🚀 PaymentForm: Initializing...');
28
+ console.log('🔍 PaymentAPI object:', window.PaymentAPI);
29
+ console.log('🔍 PaymentAPI.currencies:', window.PaymentAPI?.currencies);
30
+ console.log('🔍 PaymentAPI.admin:', window.PaymentAPI?.admin);
27
31
  await this.loadInitialData();
28
32
  },
29
33
 
30
34
  async loadInitialData() {
35
+ console.log('📊 PaymentForm: Loading initial data...');
31
36
  this.loading = true;
32
37
  try {
33
38
  // Load all currencies and provider-specific currencies
@@ -36,8 +41,9 @@ function paymentForm() {
36
41
  this.loadProviderCurrencies(),
37
42
  this.loadUsers()
38
43
  ]);
44
+ console.log('✅ PaymentForm: Initial data loaded successfully');
39
45
  } catch (error) {
40
- console.error('Failed to load initial data:', error);
46
+ console.error('❌ PaymentForm: Failed to load initial data:', error);
41
47
  PaymentAPI.utils.showNotification('Failed to load form data', 'error');
42
48
  } finally {
43
49
  this.loading = false;
@@ -45,20 +51,42 @@ function paymentForm() {
45
51
  },
46
52
 
47
53
  async loadAllCurrencies() {
54
+ console.log('💰 PaymentForm: Loading all currencies...');
55
+ console.log('🔍 PaymentAPI.currencies.supported type:', typeof PaymentAPI.currencies.supported);
48
56
  try {
57
+ if (typeof PaymentAPI.currencies.supported !== 'function') {
58
+ console.error('❌ PaymentAPI.currencies.supported is not a function:', PaymentAPI.currencies.supported);
59
+ console.log('🔍 Available currencies methods:', Object.keys(PaymentAPI.currencies));
60
+ return;
61
+ }
49
62
  const data = await PaymentAPI.currencies.supported();
50
- this.allCurrencies = data.currencies || [];
63
+ console.log('📊 Currencies API response:', data);
64
+ this.allCurrencies = data.currencies?.currencies || data.currencies || [];
65
+ console.log('✅ Loaded currencies:', this.allCurrencies.length);
51
66
  } catch (error) {
52
- console.error('Failed to load currencies:', error);
67
+ console.error('Failed to load currencies:', error);
53
68
  }
54
69
  },
55
70
 
56
71
  async loadProviderCurrencies() {
57
- if (!this.form.provider) return;
72
+ if (!this.form.provider) {
73
+ console.log('⚠️ PaymentForm: No provider selected, skipping currency load');
74
+ return;
75
+ }
76
+
77
+ console.log('🏦 PaymentForm: Loading provider currencies for:', this.form.provider);
78
+ console.log('🔍 PaymentAPI.currencies.byProvider type:', typeof PaymentAPI.currencies.byProvider);
58
79
 
59
80
  this.loadingCurrencies = true;
60
81
  try {
61
- const data = await PaymentAPI.currencies.providerConfigs(this.form.provider);
82
+ if (typeof PaymentAPI.currencies.byProvider !== 'function') {
83
+ console.error('❌ PaymentAPI.currencies.byProvider is not a function:', PaymentAPI.currencies.byProvider);
84
+ console.log('🔍 Available currencies methods:', Object.keys(PaymentAPI.currencies));
85
+ return;
86
+ }
87
+
88
+ const data = await PaymentAPI.currencies.byProvider(this.form.provider);
89
+ console.log('📊 Provider currencies API response:', data);
62
90
  this.currencies = data.results || data.currencies || [];
63
91
 
64
92
  // Transform provider currency data for display
@@ -76,13 +104,16 @@ function paymentForm() {
76
104
  provider_code: pc.provider_currency_code
77
105
  }));
78
106
 
107
+ console.log('✅ Loaded provider currencies:', this.currencies.length);
108
+
79
109
  // If current currency is not supported by provider, reset it
80
110
  if (this.form.currency_code && !this.currencies.find(c => c.code === this.form.currency_code)) {
111
+ console.log('⚠️ Current currency not supported by provider, resetting');
81
112
  this.form.currency_code = '';
82
113
  this.conversionResult = null;
83
114
  }
84
115
  } catch (error) {
85
- console.error('Failed to load provider currencies:', error);
116
+ console.error('Failed to load provider currencies:', error);
86
117
  this.currencies = [];
87
118
  } finally {
88
119
  this.loadingCurrencies = false;
@@ -90,17 +121,43 @@ function paymentForm() {
90
121
  },
91
122
 
92
123
  async loadUsers() {
124
+ console.log('👥 PaymentForm: Loading users...');
125
+ console.log('🔍 PaymentAPI.admin:', PaymentAPI.admin);
126
+ console.log('🔍 PaymentAPI.admin?.users:', PaymentAPI.admin?.users);
127
+ console.log('🔍 PaymentAPI.admin?.users?.list type:', typeof PaymentAPI.admin?.users?.list);
128
+
93
129
  try {
130
+ if (!PaymentAPI.admin) {
131
+ console.error('❌ PaymentAPI.admin is undefined');
132
+ this.users = [{ id: '', username: 'Select User', email: '' }];
133
+ return;
134
+ }
135
+
136
+ if (!PaymentAPI.admin.users) {
137
+ console.error('❌ PaymentAPI.admin.users is undefined');
138
+ this.users = [{ id: '', username: 'Select User', email: '' }];
139
+ return;
140
+ }
141
+
142
+ if (typeof PaymentAPI.admin.users.list !== 'function') {
143
+ console.error('❌ PaymentAPI.admin.users.list is not a function:', PaymentAPI.admin.users.list);
144
+ this.users = [{ id: '', username: 'Select User', email: '' }];
145
+ return;
146
+ }
147
+
94
148
  const data = await PaymentAPI.admin.users.list();
149
+ console.log('📊 Users API response:', data);
95
150
  this.users = data.results || data || [];
96
151
 
97
152
  // If no users loaded, try to get current user info
98
153
  if (this.users.length === 0) {
99
- console.warn('No users loaded from admin API');
154
+ console.warn('⚠️ No users loaded from admin API');
100
155
  this.users = [{ id: '', username: 'Select User', email: '' }];
156
+ } else {
157
+ console.log('✅ Loaded users:', this.users.length);
101
158
  }
102
159
  } catch (error) {
103
- console.error('Failed to load users:', error);
160
+ console.error('Failed to load users:', error);
104
161
  // Set empty option for user selection
105
162
  this.users = [{ id: '', username: 'Select User', email: '' }];
106
163
  }
@@ -0,0 +1,39 @@
1
+ """
2
+ Payments Background Tasks
3
+
4
+ Dramatiq tasks for usage tracking, statistics, and payment processing.
5
+ """
6
+
7
+ from .usage_tracking import (
8
+ update_api_key_usage_async,
9
+ update_subscription_usage_async,
10
+ batch_update_usage_counters,
11
+ cleanup_stale_usage_cache
12
+ )
13
+
14
+ from .types import (
15
+ TaskResult,
16
+ UsageUpdateRequest,
17
+ UsageUpdateResult,
18
+ BatchUpdateRequest,
19
+ BatchUpdateResult,
20
+ CleanupResult,
21
+ CacheStats
22
+ )
23
+
24
+ __all__ = [
25
+ # Usage tracking tasks
26
+ 'update_api_key_usage_async',
27
+ 'update_subscription_usage_async',
28
+ 'batch_update_usage_counters',
29
+ 'cleanup_stale_usage_cache',
30
+
31
+ # Pydantic types
32
+ 'TaskResult',
33
+ 'UsageUpdateRequest',
34
+ 'UsageUpdateResult',
35
+ 'BatchUpdateRequest',
36
+ 'BatchUpdateResult',
37
+ 'CleanupResult',
38
+ 'CacheStats',
39
+ ]
@@ -0,0 +1,73 @@
1
+ """
2
+ Pydantic types for background tasks.
3
+ """
4
+
5
+ from typing import Optional, Dict, Any, List
6
+ from pydantic import BaseModel, Field, ConfigDict
7
+ from datetime import datetime
8
+
9
+
10
+ class TaskResult(BaseModel):
11
+ """Base result type for all background tasks."""
12
+ model_config = ConfigDict(validate_assignment=True)
13
+
14
+ status: str = Field(description="Task execution status")
15
+ message: Optional[str] = Field(None, description="Human-readable message")
16
+ error: Optional[str] = Field(None, description="Error message if failed")
17
+ processing_time_ms: Optional[float] = Field(None, description="Processing time in milliseconds")
18
+ timestamp: datetime = Field(default_factory=datetime.utcnow, description="Task completion timestamp")
19
+
20
+
21
+ class UsageUpdateRequest(BaseModel):
22
+ """Request for updating usage counters."""
23
+ model_config = ConfigDict(validate_assignment=True)
24
+
25
+ resource_id: str = Field(description="Resource ID (API key or subscription)")
26
+ increment: int = Field(default=1, description="Amount to increment")
27
+ ip_address: Optional[str] = Field(None, description="Client IP address")
28
+ metadata: Dict[str, Any] = Field(default_factory=dict, description="Additional metadata")
29
+
30
+
31
+ class UsageUpdateResult(TaskResult):
32
+ """Result of usage update operation."""
33
+
34
+ resource_id: str = Field(description="Updated resource ID")
35
+ total_requests: Optional[int] = Field(None, description="Total requests after update")
36
+ increment: int = Field(description="Amount incremented")
37
+ user_id: Optional[int] = Field(None, description="Associated user ID")
38
+
39
+
40
+ class BatchUpdateRequest(BaseModel):
41
+ """Request for batch usage updates."""
42
+ model_config = ConfigDict(validate_assignment=True)
43
+
44
+ api_key_updates: List[UsageUpdateRequest] = Field(default_factory=list, description="API key updates")
45
+ subscription_updates: List[UsageUpdateRequest] = Field(default_factory=list, description="Subscription updates")
46
+ force_flush: bool = Field(default=False, description="Force immediate processing")
47
+
48
+
49
+ class BatchUpdateResult(TaskResult):
50
+ """Result of batch update operation."""
51
+
52
+ api_keys_updated: int = Field(default=0, description="Number of API keys updated")
53
+ subscriptions_updated: int = Field(default=0, description="Number of subscriptions updated")
54
+ errors: List[Dict[str, Any]] = Field(default_factory=list, description="Processing errors")
55
+ total_items: int = Field(description="Total items processed")
56
+
57
+
58
+ class CleanupResult(TaskResult):
59
+ """Result of cleanup operation."""
60
+
61
+ cleaned_entries: int = Field(default=0, description="Number of entries cleaned")
62
+ cleanup_type: str = Field(description="Type of cleanup performed")
63
+ cutoff_date: Optional[datetime] = Field(None, description="Cleanup cutoff date")
64
+
65
+
66
+ class CacheStats(BaseModel):
67
+ """Cache statistics."""
68
+ model_config = ConfigDict(validate_assignment=True)
69
+
70
+ total_keys: int = Field(description="Total cache keys")
71
+ expired_keys: int = Field(description="Expired cache keys")
72
+ memory_usage_mb: Optional[float] = Field(None, description="Memory usage in MB")
73
+ hit_rate: Optional[float] = Field(None, description="Cache hit rate percentage")
@@ -0,0 +1,308 @@
1
+ """
2
+ Background tasks for API usage tracking and statistics.
3
+ """
4
+ import dramatiq
5
+ import logging
6
+ import time
7
+ from typing import Dict, Any, List
8
+ from django.db import transaction
9
+ from django.db.models import F
10
+ from django.utils import timezone
11
+ from django.core.cache import cache
12
+
13
+ from ..models import APIKey, Subscription
14
+ from .types import (
15
+ TaskResult,
16
+ UsageUpdateRequest,
17
+ UsageUpdateResult,
18
+ BatchUpdateResult,
19
+ CleanupResult
20
+ )
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+ @dramatiq.actor(queue_name="payments")
25
+ def update_api_key_usage_async(
26
+ api_key_id: str,
27
+ ip_address: str = None,
28
+ increment: int = 1
29
+ ) -> UsageUpdateResult:
30
+ """
31
+ Update API key usage counters asynchronously.
32
+
33
+ Args:
34
+ api_key_id: API key UUID to update
35
+ ip_address: Client IP address for logging
36
+ increment: Number to increment (default: 1)
37
+
38
+ Returns:
39
+ Update result with statistics
40
+ """
41
+ start_time = time.time()
42
+
43
+ try:
44
+ with transaction.atomic():
45
+ # Use F() expressions for atomic updates
46
+ updated_count = APIKey.objects.filter(id=api_key_id).update(
47
+ total_requests=F('total_requests') + increment,
48
+ last_used_at=timezone.now(),
49
+ updated_at=timezone.now()
50
+ )
51
+
52
+ if updated_count == 0:
53
+ logger.warning(f"API key not found: {api_key_id}")
54
+ return UsageUpdateResult(
55
+ status='error',
56
+ error='API key not found',
57
+ resource_id=api_key_id,
58
+ increment=increment
59
+ )
60
+
61
+ # Get updated values for logging
62
+ api_key = APIKey.objects.get(id=api_key_id)
63
+
64
+ processing_time = (time.time() - start_time) * 1000
65
+
66
+ logger.debug(f"API key usage updated", extra={
67
+ 'api_key_id': api_key_id,
68
+ 'user_id': api_key.user.id,
69
+ 'total_requests': api_key.total_requests,
70
+ 'increment': increment,
71
+ 'ip_address': ip_address,
72
+ 'processing_time_ms': round(processing_time, 2)
73
+ })
74
+
75
+ return UsageUpdateResult(
76
+ status='success',
77
+ resource_id=api_key_id,
78
+ total_requests=api_key.total_requests,
79
+ increment=increment,
80
+ user_id=api_key.user.id,
81
+ processing_time_ms=round(processing_time, 2)
82
+ )
83
+
84
+ except Exception as e:
85
+ logger.error(f"Failed to update API key usage", extra={
86
+ 'api_key_id': api_key_id,
87
+ 'error': str(e),
88
+ 'ip_address': ip_address
89
+ })
90
+ raise # Re-raise for Dramatiq retry logic
91
+
92
+ @dramatiq.actor(queue_name="payments")
93
+ def update_subscription_usage_async(
94
+ subscription_id: str,
95
+ increment: int = 1
96
+ ) -> UsageUpdateResult:
97
+ """
98
+ Update subscription usage counters asynchronously.
99
+
100
+ Args:
101
+ subscription_id: Subscription UUID to update
102
+ increment: Number to increment (default: 1)
103
+
104
+ Returns:
105
+ Update result with statistics
106
+ """
107
+ start_time = time.time()
108
+
109
+ try:
110
+ with transaction.atomic():
111
+ # Use F() expressions for atomic updates
112
+ updated_count = Subscription.objects.filter(id=subscription_id).update(
113
+ total_requests=F('total_requests') + increment,
114
+ last_request_at=timezone.now(),
115
+ updated_at=timezone.now()
116
+ )
117
+
118
+ if updated_count == 0:
119
+ logger.warning(f"Subscription not found: {subscription_id}")
120
+ return UsageUpdateResult(
121
+ status='error',
122
+ error='Subscription not found',
123
+ resource_id=subscription_id,
124
+ increment=increment
125
+ )
126
+
127
+ # Get updated values for logging
128
+ subscription = Subscription.objects.get(id=subscription_id)
129
+
130
+ processing_time = (time.time() - start_time) * 1000
131
+
132
+ logger.debug(f"Subscription usage updated", extra={
133
+ 'subscription_id': subscription_id,
134
+ 'user_id': subscription.user.id,
135
+ 'total_requests': subscription.total_requests,
136
+ 'increment': increment,
137
+ 'processing_time_ms': round(processing_time, 2)
138
+ })
139
+
140
+ return UsageUpdateResult(
141
+ status='success',
142
+ resource_id=subscription_id,
143
+ total_requests=subscription.total_requests,
144
+ increment=increment,
145
+ user_id=subscription.user.id,
146
+ processing_time_ms=round(processing_time, 2)
147
+ )
148
+
149
+ except Exception as e:
150
+ logger.error(f"Failed to update subscription usage", extra={
151
+ 'subscription_id': subscription_id,
152
+ 'error': str(e)
153
+ })
154
+ raise # Re-raise for Dramatiq retry logic
155
+
156
+ @dramatiq.actor(queue_name="payments")
157
+ def batch_update_usage_counters() -> BatchUpdateResult:
158
+ """
159
+ Batch update usage counters from cache to reduce database load.
160
+
161
+ This task processes accumulated usage data from Redis cache
162
+ and performs batch updates to the database.
163
+
164
+ Returns:
165
+ Batch processing results
166
+ """
167
+ start_time = time.time()
168
+ api_keys_updated = 0
169
+ subscriptions_updated = 0
170
+ errors = []
171
+
172
+ try:
173
+ # Process API key usage counters
174
+ api_key_pattern = "api_usage_pending:*"
175
+ api_key_keys = cache.keys(api_key_pattern)
176
+
177
+ for cache_key in api_key_keys:
178
+ try:
179
+ # Extract API key ID from cache key
180
+ api_key_id = cache_key.split(':')[-1]
181
+ pending_count = cache.get(cache_key, 0)
182
+
183
+ if pending_count > 0:
184
+ # Update in background
185
+ update_api_key_usage_async.send(
186
+ api_key_id=api_key_id,
187
+ increment=pending_count
188
+ )
189
+
190
+ # Clear cache
191
+ cache.delete(cache_key)
192
+ api_keys_updated += 1
193
+
194
+ except Exception as e:
195
+ errors.append({
196
+ 'type': 'api_key',
197
+ 'cache_key': cache_key,
198
+ 'error': str(e)
199
+ })
200
+
201
+ # Process subscription usage counters
202
+ subscription_pattern = "subscription_usage_pending:*"
203
+ subscription_keys = cache.keys(subscription_pattern)
204
+
205
+ for cache_key in subscription_keys:
206
+ try:
207
+ # Extract subscription ID from cache key
208
+ subscription_id = cache_key.split(':')[-1]
209
+ pending_count = cache.get(cache_key, 0)
210
+
211
+ if pending_count > 0:
212
+ # Update in background
213
+ update_subscription_usage_async.send(
214
+ subscription_id=subscription_id,
215
+ increment=pending_count
216
+ )
217
+
218
+ # Clear cache
219
+ cache.delete(cache_key)
220
+ subscriptions_updated += 1
221
+
222
+ except Exception as e:
223
+ errors.append({
224
+ 'type': 'subscription',
225
+ 'cache_key': cache_key,
226
+ 'error': str(e)
227
+ })
228
+
229
+ processing_time = (time.time() - start_time) * 1000
230
+
231
+ logger.info(f"Batch usage update completed", extra={
232
+ 'api_keys_updated': api_keys_updated,
233
+ 'subscriptions_updated': subscriptions_updated,
234
+ 'errors_count': len(errors),
235
+ 'processing_time_ms': round(processing_time, 2)
236
+ })
237
+
238
+ return BatchUpdateResult(
239
+ status='success',
240
+ api_keys_updated=api_keys_updated,
241
+ subscriptions_updated=subscriptions_updated,
242
+ errors=errors,
243
+ total_items=api_keys_updated + subscriptions_updated,
244
+ processing_time_ms=round(processing_time, 2)
245
+ )
246
+
247
+ except Exception as e:
248
+ logger.error(f"Batch usage update failed: {e}")
249
+ errors.append({
250
+ 'type': 'batch_processing',
251
+ 'error': str(e)
252
+ })
253
+ return BatchUpdateResult(
254
+ status='error',
255
+ api_keys_updated=api_keys_updated,
256
+ subscriptions_updated=subscriptions_updated,
257
+ errors=errors,
258
+ total_items=api_keys_updated + subscriptions_updated,
259
+ error=str(e)
260
+ )
261
+
262
+ @dramatiq.actor(queue_name="payments")
263
+ def cleanup_stale_usage_cache() -> CleanupResult:
264
+ """
265
+ Cleanup stale usage tracking cache entries.
266
+
267
+ Removes old cache entries that might have been left behind
268
+ due to processing errors or system restarts.
269
+
270
+ Returns:
271
+ Cleanup results
272
+ """
273
+ try:
274
+ cleanup_count = 0
275
+
276
+ # Cleanup old API key usage cache
277
+ api_key_keys = cache.keys("api_usage_pending:*")
278
+ for key in api_key_keys:
279
+ # Check if cache entry is older than 1 hour
280
+ ttl = cache.ttl(key)
281
+ if ttl is not None and ttl < 3600: # Less than 1 hour remaining
282
+ cache.delete(key)
283
+ cleanup_count += 1
284
+
285
+ # Cleanup old subscription usage cache
286
+ subscription_keys = cache.keys("subscription_usage_pending:*")
287
+ for key in subscription_keys:
288
+ ttl = cache.ttl(key)
289
+ if ttl is not None and ttl < 3600:
290
+ cache.delete(key)
291
+ cleanup_count += 1
292
+
293
+ logger.info(f"Cleaned up {cleanup_count} stale cache entries")
294
+
295
+ return CleanupResult(
296
+ status='completed',
297
+ cleaned_entries=cleanup_count,
298
+ cleanup_type='stale_usage_cache'
299
+ )
300
+
301
+ except Exception as e:
302
+ logger.error(f"Cache cleanup failed: {e}")
303
+ return CleanupResult(
304
+ status='error',
305
+ error=str(e),
306
+ cleaned_entries=0,
307
+ cleanup_type='stale_usage_cache'
308
+ )
@@ -0,0 +1,23 @@
1
+ {% comment %}
2
+ Reusable dashboard header component
3
+
4
+ Usage:
5
+ {% include "admin/payments/_components/dashboard_header.html" with title="Payment Overview" icon="💳" %}
6
+
7
+ Parameters:
8
+ - title: Dashboard title
9
+ - icon: Emoji or icon to display
10
+ - subtitle: Optional subtitle
11
+ {% endcomment %}
12
+
13
+ <div class="bg-white border border-base-200 dark:bg-base-900 dark:border-base-700 p-4 rounded-default mb-4">
14
+ <h3 class="text-lg font-semibold text-font-important-light dark:text-font-important-dark mb-3">
15
+ {% if icon %}{{ icon }} {% endif %}{{ title }}
16
+ {% if subtitle %}
17
+ <span class="text-sm font-normal text-font-subtle-light dark:text-font-subtle-dark">{{ subtitle }}</span>
18
+ {% endif %}
19
+ </h3>
20
+
21
+ {% block dashboard_content %}
22
+ {% endblock %}
23
+ </div>
@@ -0,0 +1,25 @@
1
+ {% comment %}
2
+ Reusable stats card component for payment admin dashboards
3
+
4
+ Usage:
5
+ {% include "admin/payments/_components/stats_card.html" with title="Total Users" value="1,234" color="primary" icon="👥" %}
6
+
7
+ Parameters:
8
+ - title: Card title
9
+ - value: Main value to display
10
+ - color: Color theme (primary, success, warning, error, info)
11
+ - icon: Emoji or icon to display
12
+ - subtitle: Optional subtitle text
13
+ {% endcomment %}
14
+
15
+ {% load humanize %}
16
+
17
+ <div class="text-center bg-base-50 dark:bg-base-800 p-3 rounded-default">
18
+ <p class="text-2xl font-bold text-{{ color|default:'primary' }}-600 dark:text-{{ color|default:'primary' }}-400">
19
+ {% if icon %}<span class="mr-1">{{ icon }}</span>{% endif %}{{ value|default:0 }}
20
+ </p>
21
+ <p class="text-sm text-font-subtle-light dark:text-font-subtle-dark">{{ title }}</p>
22
+ {% if subtitle %}
23
+ <p class="text-xs text-gray-500">{{ subtitle }}</p>
24
+ {% endif %}
25
+ </div>
@@ -0,0 +1,16 @@
1
+ {% comment %}
2
+ Reusable stats grid component for payment admin dashboards
3
+
4
+ Usage:
5
+ {% include "admin/payments/_components/stats_grid.html" with stats=stats_data columns=4 %}
6
+
7
+ Parameters:
8
+ - stats: List of stat objects with title, value, color, icon properties
9
+ - columns: Number of columns (2, 3, 4, 5)
10
+ {% endcomment %}
11
+
12
+ <div class="grid grid-cols-{{ columns|default:4 }} gap-4 mb-4">
13
+ {% for stat in stats %}
14
+ {% include "admin/payments/_components/stats_card.html" with title=stat.title value=stat.value color=stat.color icon=stat.icon subtitle=stat.subtitle %}
15
+ {% endfor %}
16
+ </div>
@@ -0,0 +1,39 @@
1
+ {% extends "admin/change_list.html" %}
2
+ {% load static %}
3
+
4
+ {% block result_list %}
5
+ <!-- API Key Statistics Dashboard -->
6
+ {% include "admin/payments/_components/dashboard_header.html" with title="API Keys Overview" icon="🔑" %}
7
+
8
+ {% if api_key_stats %}
9
+ {% with stats=api_key_stats %}
10
+ <div class="grid grid-cols-4 gap-4 mb-4">
11
+ {% include "admin/payments/_components/stats_card.html" with title="Total Keys" value=stats.total_keys color="primary" %}
12
+ {% include "admin/payments/_components/stats_card.html" with title="Active" value=stats.active_keys color="success" %}
13
+ {% include "admin/payments/_components/stats_card.html" with title="Expiring Soon" value=stats.expiring_soon color="warning" %}
14
+ {% include "admin/payments/_components/stats_card.html" with title="Expired" value=stats.expired_keys color="error" %}
15
+ </div>
16
+ {% endwith %}
17
+ {% endif %}
18
+
19
+ <div class="grid grid-cols-2 gap-4">
20
+ <div class="bg-base-50 dark:bg-base-800 p-3 rounded-default">
21
+ <h4 class="font-semibold text-font-important-light dark:text-font-important-dark mb-2">Usage Statistics</h4>
22
+ <ul class="text-sm space-y-1 text-font-default-light dark:text-font-default-dark">
23
+ <li>📊 Total Requests: <span class="font-medium text-primary-600 dark:text-primary-400">{{ stats.total_requests|default:0 }}</span></li>
24
+ <li>📈 Recently Used: <span class="font-medium text-success-600 dark:text-success-400">{{ stats.recently_used|default:0 }}</span></li>
25
+ <li>🆕 Unused: <span class="font-medium text-warning-600 dark:text-warning-400">{{ stats.unused_keys|default:0 }}</span></li>
26
+ </ul>
27
+ </div>
28
+ <div class="bg-base-50 dark:bg-base-800 p-3 rounded-default">
29
+ <h4 class="font-semibold text-font-important-light dark:text-font-important-dark mb-2">🔒 Security</h4>
30
+ <ul class="text-sm space-y-1 text-font-default-light dark:text-font-default-dark">
31
+ <li>🔥 Heavy Usage: <span class="font-medium text-purple-600 dark:text-purple-400">{{ stats.heavy_usage_keys|default:0 }}</span></li>
32
+ <li>⚠️ Old Unused: <span class="font-medium text-error-600 dark:text-error-400">{{ stats.never_used_old_keys|default:0 }}</span></li>
33
+ <li>👥 Top Users: <span class="font-medium text-info-600 dark:text-info-400">{{ stats.top_users|length|default:0 }}</span></li>
34
+ </ul>
35
+ </div>
36
+ </div>
37
+
38
+ {{ block.super }}
39
+ {% endblock %}