django-cfg 1.3.11__py3-none-any.whl → 1.4.0__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 (439) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/accounts/admin/inlines.py +11 -5
  3. django_cfg/apps/accounts/admin/user_admin.py +39 -16
  4. django_cfg/apps/accounts/serializers/profile.py +1 -1
  5. django_cfg/apps/accounts/services/otp_service.py +18 -11
  6. django_cfg/apps/accounts/signals.py +15 -24
  7. django_cfg/apps/accounts/utils/notifications.py +217 -358
  8. django_cfg/apps/accounts/views/otp.py +2 -2
  9. django_cfg/apps/accounts/views/webhook.py +1 -1
  10. django_cfg/apps/agents/core/django_agent.py +1 -1
  11. django_cfg/apps/api/commands/views.py +66 -83
  12. django_cfg/apps/api/health/drf_views.py +269 -0
  13. django_cfg/apps/api/health/serializers.py +45 -0
  14. django_cfg/apps/api/health/urls.py +6 -1
  15. django_cfg/apps/knowbase/admin/actions/__init__.py +13 -0
  16. django_cfg/apps/knowbase/admin/actions/visibility_actions.py +56 -0
  17. django_cfg/apps/knowbase/admin/document_admin.py +136 -270
  18. django_cfg/apps/knowbase/admin/helpers/__init__.py +17 -0
  19. django_cfg/apps/knowbase/admin/helpers/configs.py +72 -0
  20. django_cfg/apps/knowbase/admin/helpers/display_helpers.py +156 -0
  21. django_cfg/apps/knowbase/admin/helpers/statistics.py +108 -0
  22. django_cfg/apps/knowbase/config/constance_fields.py +1 -1
  23. django_cfg/apps/knowbase/config/settings.py +2 -2
  24. django_cfg/apps/knowbase/mixins/__init__.py +19 -2
  25. django_cfg/apps/knowbase/mixins/config/__init__.py +14 -0
  26. django_cfg/apps/knowbase/mixins/config/defaults.py +75 -0
  27. django_cfg/apps/knowbase/mixins/config/meta_config.py +120 -0
  28. django_cfg/apps/knowbase/mixins/creator.py +10 -10
  29. django_cfg/apps/knowbase/mixins/external_data_mixin.py +105 -403
  30. django_cfg/apps/knowbase/mixins/generators/__init__.py +16 -0
  31. django_cfg/apps/knowbase/mixins/generators/content_generator.py +218 -0
  32. django_cfg/apps/knowbase/mixins/generators/field_analyzer.py +76 -0
  33. django_cfg/apps/knowbase/mixins/generators/metadata_generator.py +124 -0
  34. django_cfg/apps/knowbase/mixins/service.py +2 -2
  35. django_cfg/apps/knowbase/services/archive/__init__.py +1 -0
  36. django_cfg/apps/knowbase/services/archive/analyzers/__init__.py +17 -0
  37. django_cfg/apps/knowbase/services/archive/analyzers/complexity_analyzer.py +33 -0
  38. django_cfg/apps/knowbase/services/archive/analyzers/purpose_detector.py +36 -0
  39. django_cfg/apps/knowbase/services/archive/analyzers/quality_analyzer.py +39 -0
  40. django_cfg/apps/knowbase/services/archive/analyzers/tag_generator.py +103 -0
  41. django_cfg/apps/knowbase/services/archive/chunking/__init__.py +19 -0
  42. django_cfg/apps/knowbase/services/archive/chunking/base.py +81 -0
  43. django_cfg/apps/knowbase/services/archive/chunking/json_chunker.py +62 -0
  44. django_cfg/apps/knowbase/services/archive/chunking/markdown_chunker.py +107 -0
  45. django_cfg/apps/knowbase/services/archive/chunking/python_chunker.py +248 -0
  46. django_cfg/apps/knowbase/services/archive/chunking/text_chunker.py +70 -0
  47. django_cfg/apps/knowbase/services/archive/chunking_service.py +110 -729
  48. django_cfg/apps/knowbase/services/archive/context/__init__.py +14 -0
  49. django_cfg/apps/knowbase/services/archive/context/builders.py +220 -0
  50. django_cfg/apps/knowbase/services/archive/context/models.py +38 -0
  51. django_cfg/apps/knowbase/services/embedding/models.py +18 -14
  52. django_cfg/apps/knowbase/services/embedding/processors.py +6 -3
  53. django_cfg/apps/knowbase/tasks/document_processing.py +11 -3
  54. django_cfg/apps/leads/tests.py +1 -1
  55. django_cfg/apps/payments/admin/api_keys_admin.py +1 -1
  56. django_cfg/apps/payments/admin/balance_admin.py +1 -1
  57. django_cfg/apps/payments/admin/currencies_admin.py +1 -1
  58. django_cfg/apps/payments/admin/payments_admin.py +1 -1
  59. django_cfg/apps/payments/admin/subscriptions_admin.py +1 -1
  60. django_cfg/apps/payments/admin_interface/templates/payments/base.html +59 -126
  61. django_cfg/apps/payments/admin_interface/views/api/payments.py +1 -1
  62. django_cfg/apps/payments/admin_interface/views/api/stats.py +1 -1
  63. django_cfg/apps/payments/admin_interface/views/api/users.py +1 -1
  64. django_cfg/apps/payments/admin_interface/views/api/webhook_admin.py +1 -1
  65. django_cfg/apps/payments/admin_interface/views/api/webhook_public.py +1 -1
  66. django_cfg/apps/payments/admin_interface/views/base.py +29 -2
  67. django_cfg/apps/payments/apps.py +1 -1
  68. django_cfg/apps/payments/config/django_cfg_integration.py +2 -2
  69. django_cfg/apps/payments/config/helpers.py +3 -2
  70. django_cfg/apps/payments/management/commands/cleanup_expired_data.py +1 -1
  71. django_cfg/apps/payments/management/commands/currency_stats.py +1 -1
  72. django_cfg/apps/payments/management/commands/manage_currencies.py +1 -1
  73. django_cfg/apps/payments/management/commands/manage_providers.py +1 -1
  74. django_cfg/apps/payments/management/commands/process_pending_payments.py +1 -1
  75. django_cfg/apps/payments/management/commands/test_providers.py +1 -1
  76. django_cfg/apps/payments/middleware/api_access.py +1 -1
  77. django_cfg/apps/payments/middleware/rate_limiting.py +1 -1
  78. django_cfg/apps/payments/middleware/usage_tracking.py +1 -1
  79. django_cfg/apps/payments/models/balance.py +2 -2
  80. django_cfg/apps/payments/models/managers/api_key_managers.py +1 -1
  81. django_cfg/apps/payments/models/managers/balance_managers.py +1 -1
  82. django_cfg/apps/payments/models/managers/currency_managers.py +1 -1
  83. django_cfg/apps/payments/models/managers/payment_managers.py +1 -1
  84. django_cfg/apps/payments/models/managers/subscription_managers.py +1 -1
  85. django_cfg/apps/payments/models/payments.py +2 -2
  86. django_cfg/apps/payments/services/cache_service/__init__.py +1 -1
  87. django_cfg/apps/payments/services/cache_service/simple_cache.py +10 -5
  88. django_cfg/apps/payments/services/core/base.py +1 -1
  89. django_cfg/apps/payments/services/core/currency/__init__.py +13 -0
  90. django_cfg/apps/payments/services/core/currency/currency_converter.py +57 -0
  91. django_cfg/apps/payments/services/core/currency/currency_validator.py +61 -0
  92. django_cfg/apps/payments/services/core/operations/__init__.py +15 -0
  93. django_cfg/apps/payments/services/core/operations/payment_canceller.py +100 -0
  94. django_cfg/apps/payments/services/core/operations/payment_creator.py +196 -0
  95. django_cfg/apps/payments/services/core/operations/status_checker.py +100 -0
  96. django_cfg/apps/payments/services/core/payment_service.py +124 -612
  97. django_cfg/apps/payments/services/core/providers/__init__.py +13 -0
  98. django_cfg/apps/payments/services/core/providers/provider_client.py +132 -0
  99. django_cfg/apps/payments/services/core/providers/status_mapper.py +89 -0
  100. django_cfg/apps/payments/services/core/utils/__init__.py +13 -0
  101. django_cfg/apps/payments/services/core/utils/data_converter.py +48 -0
  102. django_cfg/apps/payments/services/core/utils/statistics_calculator.py +69 -0
  103. django_cfg/apps/payments/services/providers/base.py +1 -1
  104. django_cfg/apps/payments/services/providers/nowpayments/__init__.py +3 -3
  105. django_cfg/apps/payments/services/providers/nowpayments/parsers/__init__.py +9 -0
  106. django_cfg/apps/payments/services/providers/nowpayments/parsers/data/__init__.py +23 -0
  107. django_cfg/apps/payments/services/providers/nowpayments/parsers/data/constants.py +23 -0
  108. django_cfg/apps/payments/services/providers/nowpayments/parsers/data/currency_names.py +244 -0
  109. django_cfg/apps/payments/services/providers/nowpayments/parsers/data/patterns.py +511 -0
  110. django_cfg/apps/payments/services/providers/nowpayments/parsers/parser.py +168 -0
  111. django_cfg/apps/payments/services/providers/nowpayments/provider.py +1 -1
  112. django_cfg/apps/payments/services/providers/nowpayments/sync.py +1 -1
  113. django_cfg/apps/payments/services/providers/registry.py +1 -1
  114. django_cfg/apps/payments/services/providers/sync_service.py +1 -1
  115. django_cfg/apps/payments/signals/__init__.py +1 -1
  116. django_cfg/apps/payments/signals/api_key_signals.py +1 -1
  117. django_cfg/apps/payments/signals/balance_signals.py +1 -1
  118. django_cfg/apps/payments/signals/payment_signals.py +1 -1
  119. django_cfg/apps/payments/signals/subscription_signals.py +1 -1
  120. django_cfg/apps/payments/views/api/api_keys.py +1 -1
  121. django_cfg/apps/payments/views/api/balances.py +1 -1
  122. django_cfg/apps/payments/views/api/base.py +1 -1
  123. django_cfg/apps/payments/views/api/currencies.py +1 -1
  124. django_cfg/apps/payments/views/api/payments.py +1 -1
  125. django_cfg/apps/payments/views/api/subscriptions.py +1 -1
  126. django_cfg/apps/payments/views/api/webhooks.py +1 -1
  127. django_cfg/apps/payments/views/serializers/api_keys.py +1 -1
  128. django_cfg/apps/payments/views/serializers/balances.py +1 -1
  129. django_cfg/apps/payments/views/serializers/currencies.py +1 -1
  130. django_cfg/apps/payments/views/serializers/payments.py +1 -1
  131. django_cfg/apps/payments/views/serializers/subscriptions.py +1 -1
  132. django_cfg/apps/payments/views/serializers/webhooks.py +1 -1
  133. django_cfg/apps/support/admin/support_admin.py +21 -13
  134. django_cfg/apps/support/templates/support/chat/access_denied.html +21 -27
  135. django_cfg/apps/support/templates/support/chat/ticket_chat.html +183 -254
  136. django_cfg/apps/support/utils/support_email_service.py +1 -1
  137. django_cfg/apps/tasks/templates/tasks/layout/base.html +20 -115
  138. django_cfg/apps/tasks/utils/simulator.py +1 -1
  139. django_cfg/apps/tasks/views/dashboard.py +33 -3
  140. django_cfg/apps/urls.py +5 -1
  141. django_cfg/cli/README.md +57 -471
  142. django_cfg/cli/commands/create_project.py +140 -529
  143. django_cfg/cli/main.py +13 -10
  144. django_cfg/core/__init__.py +63 -6
  145. django_cfg/core/base/__init__.py +5 -0
  146. django_cfg/core/base/config_model.py +652 -0
  147. django_cfg/core/builders/__init__.py +11 -0
  148. django_cfg/core/builders/apps_builder.py +258 -0
  149. django_cfg/core/builders/middleware_builder.py +115 -0
  150. django_cfg/core/builders/security_builder.py +96 -0
  151. django_cfg/core/config.py +20 -892
  152. django_cfg/core/constants.py +69 -0
  153. django_cfg/core/environment/__init__.py +9 -0
  154. django_cfg/core/exceptions.py +45 -298
  155. django_cfg/core/generation/__init__.py +51 -0
  156. django_cfg/core/generation/core_generators/__init__.py +0 -0
  157. django_cfg/core/generation/core_generators/settings.py +90 -0
  158. django_cfg/core/generation/core_generators/static.py +82 -0
  159. django_cfg/core/generation/core_generators/templates.py +141 -0
  160. django_cfg/core/generation/data_generators/__init__.py +15 -0
  161. django_cfg/core/generation/data_generators/cache.py +132 -0
  162. django_cfg/core/generation/data_generators/database.py +117 -0
  163. django_cfg/core/generation/generation.py +92 -0
  164. django_cfg/core/generation/integration_generators/__init__.py +21 -0
  165. django_cfg/core/generation/integration_generators/api.py +237 -0
  166. django_cfg/core/generation/integration_generators/sessions.py +65 -0
  167. django_cfg/core/generation/integration_generators/tailwind.py +54 -0
  168. django_cfg/core/generation/integration_generators/tasks.py +92 -0
  169. django_cfg/core/generation/integration_generators/third_party.py +144 -0
  170. django_cfg/core/generation/orchestrator.py +285 -0
  171. django_cfg/core/generation/protocols.py +30 -0
  172. django_cfg/core/generation/security_generators/__init__.py +0 -0
  173. django_cfg/core/generation/utility_generators/__init__.py +24 -0
  174. django_cfg/core/generation/utility_generators/email.py +58 -0
  175. django_cfg/core/generation/utility_generators/i18n.py +66 -0
  176. django_cfg/core/generation/utility_generators/limits.py +58 -0
  177. django_cfg/core/generation/utility_generators/logging.py +66 -0
  178. django_cfg/core/generation/utility_generators/security.py +101 -0
  179. django_cfg/core/generation/utils/__init__.py +0 -0
  180. django_cfg/core/generation/utils/helpers.py +32 -0
  181. django_cfg/core/integration/__init__.py +18 -25
  182. django_cfg/core/integration/display/startup.py +146 -133
  183. django_cfg/core/integration/url_integration.py +13 -2
  184. django_cfg/core/services/__init__.py +5 -0
  185. django_cfg/core/services/config_service.py +121 -0
  186. django_cfg/core/state/__init__.py +9 -0
  187. django_cfg/core/state/registry.py +84 -0
  188. django_cfg/core/types/__init__.py +15 -0
  189. django_cfg/core/types/aliases.py +15 -0
  190. django_cfg/core/types/enums.py +49 -0
  191. django_cfg/dashboard/DEBUG_README.md +105 -0
  192. django_cfg/dashboard/REFACTORING_SUMMARY.md +237 -0
  193. django_cfg/dashboard/__init__.py +24 -0
  194. django_cfg/dashboard/components.py +308 -0
  195. django_cfg/dashboard/debug.py +176 -0
  196. django_cfg/dashboard/management/__init__.py +0 -0
  197. django_cfg/dashboard/management/commands/__init__.py +0 -0
  198. django_cfg/dashboard/management/commands/debug_dashboard.py +109 -0
  199. django_cfg/dashboard/sections/__init__.py +1 -0
  200. django_cfg/dashboard/sections/base.py +128 -0
  201. django_cfg/dashboard/sections/commands.py +32 -0
  202. django_cfg/dashboard/sections/overview.py +394 -0
  203. django_cfg/dashboard/sections/stats.py +48 -0
  204. django_cfg/dashboard/sections/system.py +73 -0
  205. django_cfg/management/commands/check_settings.py +6 -2
  206. django_cfg/management/commands/clear_constance.py +6 -1
  207. django_cfg/management/commands/create_token.py +5 -4
  208. django_cfg/management/commands/generate.py +5 -0
  209. django_cfg/management/commands/list_urls.py +7 -2
  210. django_cfg/management/commands/migrate_all.py +6 -2
  211. django_cfg/management/commands/migrator.py +6 -1
  212. django_cfg/management/commands/rundramatiq.py +6 -1
  213. django_cfg/management/commands/rundramatiq_simulator.py +11 -4
  214. django_cfg/management/commands/runserver_ngrok.py +9 -7
  215. django_cfg/management/commands/script.py +25 -21
  216. django_cfg/management/commands/show_config.py +6 -1
  217. django_cfg/management/commands/show_urls.py +8 -3
  218. django_cfg/management/commands/superuser.py +5 -4
  219. django_cfg/management/commands/task_clear.py +8 -3
  220. django_cfg/management/commands/task_status.py +8 -3
  221. django_cfg/management/commands/test_email.py +6 -1
  222. django_cfg/management/commands/test_telegram.py +6 -1
  223. django_cfg/management/commands/test_twilio.py +6 -1
  224. django_cfg/management/commands/tree.py +7 -4
  225. django_cfg/models/__init__.py +88 -3
  226. django_cfg/models/api/__init__.py +27 -0
  227. django_cfg/models/{api.py → api/config.py} +1 -1
  228. django_cfg/models/api/drf/__init__.py +21 -0
  229. django_cfg/models/api/drf/config.py +101 -0
  230. django_cfg/models/api/drf/redoc.py +31 -0
  231. django_cfg/models/api/drf/spectacular.py +129 -0
  232. django_cfg/models/api/drf/swagger.py +59 -0
  233. django_cfg/models/{api_keys.py → api/keys.py} +16 -6
  234. django_cfg/models/{limits.py → api/limits.py} +0 -1
  235. django_cfg/models/base/__init__.py +14 -0
  236. django_cfg/models/django/__init__.py +16 -0
  237. django_cfg/models/{constance.py → django/constance.py} +1 -1
  238. django_cfg/models/{environment.py → django/environment.py} +1 -1
  239. django_cfg/models/infrastructure/__init__.py +17 -0
  240. django_cfg/models/{cache.py → infrastructure/cache.py} +3 -2
  241. django_cfg/models/infrastructure/database/__init__.py +22 -0
  242. django_cfg/models/infrastructure/database/config.py +265 -0
  243. django_cfg/models/infrastructure/database/converters.py +91 -0
  244. django_cfg/models/infrastructure/database/parsers.py +96 -0
  245. django_cfg/models/infrastructure/database/routing.py +85 -0
  246. django_cfg/models/infrastructure/database/validators.py +170 -0
  247. django_cfg/models/{logging.py → infrastructure/logging.py} +1 -1
  248. django_cfg/models/{security.py → infrastructure/security.py} +2 -2
  249. django_cfg/models/ngrok/__init__.py +11 -0
  250. django_cfg/models/ngrok/auth.py +37 -0
  251. django_cfg/models/ngrok/config.py +77 -0
  252. django_cfg/models/ngrok/tunnel.py +35 -0
  253. django_cfg/models/payments/__init__.py +20 -0
  254. django_cfg/models/payments/api_keys.py +57 -0
  255. django_cfg/models/{payments.py → payments/config.py} +56 -154
  256. django_cfg/models/payments/providers/__init__.py +15 -0
  257. django_cfg/models/payments/providers/base.py +25 -0
  258. django_cfg/models/payments/providers/nowpayments.py +48 -0
  259. django_cfg/models/services/__init__.py +18 -0
  260. django_cfg/models/services/base.py +65 -0
  261. django_cfg/models/{email.py → services/email.py} +1 -1
  262. django_cfg/models/services/telegram.py +172 -0
  263. django_cfg/models/tasks/__init__.py +51 -0
  264. django_cfg/models/tasks/backends.py +250 -0
  265. django_cfg/models/tasks/config.py +314 -0
  266. django_cfg/models/tasks/utils.py +174 -0
  267. django_cfg/modules/base.py +18 -3
  268. django_cfg/modules/django_admin/decorators/actions.py +1 -1
  269. django_cfg/modules/django_admin/decorators/display.py +1 -1
  270. django_cfg/modules/django_admin/mixins/standalone_actions_mixin.py +1 -1
  271. django_cfg/modules/django_cfg_rpc_client/README.md +346 -0
  272. django_cfg/modules/django_cfg_rpc_client/__init__.py +51 -0
  273. django_cfg/modules/django_cfg_rpc_client/client.py +540 -0
  274. django_cfg/modules/django_cfg_rpc_client/config.py +207 -0
  275. django_cfg/modules/django_cfg_rpc_client/dashboard/README.md +517 -0
  276. django_cfg/modules/django_cfg_rpc_client/dashboard/UNFOLD_INTEGRATION.md +439 -0
  277. django_cfg/modules/django_cfg_rpc_client/dashboard/__init__.py +11 -0
  278. django_cfg/modules/django_cfg_rpc_client/dashboard/apps.py +22 -0
  279. django_cfg/modules/django_cfg_rpc_client/dashboard/monitor.py +435 -0
  280. django_cfg/modules/django_cfg_rpc_client/dashboard/static/django_cfg_rpc_dashboard/js/dashboard.js +373 -0
  281. django_cfg/modules/django_cfg_rpc_client/dashboard/templates/django_cfg_rpc_dashboard/base.html +76 -0
  282. django_cfg/modules/django_cfg_rpc_client/dashboard/templates/django_cfg_rpc_dashboard/dashboard.html +200 -0
  283. django_cfg/modules/django_cfg_rpc_client/dashboard/urls.py +22 -0
  284. django_cfg/modules/django_cfg_rpc_client/dashboard/urls_admin.py +9 -0
  285. django_cfg/modules/django_cfg_rpc_client/dashboard/views.py +251 -0
  286. django_cfg/modules/django_cfg_rpc_client/exceptions.py +201 -0
  287. django_cfg/modules/django_drf_theme/CHANGELOG.md +210 -0
  288. django_cfg/modules/django_drf_theme/EXAMPLE.md +465 -0
  289. django_cfg/modules/django_drf_theme/IMPLEMENTATION.md +232 -0
  290. django_cfg/modules/django_drf_theme/README.md +207 -0
  291. django_cfg/modules/django_drf_theme/TAILWIND_CDN_GUIDE.md +274 -0
  292. django_cfg/modules/django_drf_theme/__init__.py +23 -0
  293. django_cfg/modules/django_drf_theme/apps.py +15 -0
  294. django_cfg/modules/django_drf_theme/renderers.py +58 -0
  295. django_cfg/modules/django_drf_theme/templates/rest_framework/tailwind/api.html +375 -0
  296. django_cfg/modules/django_drf_theme/templates/rest_framework/tailwind/base.html +938 -0
  297. django_cfg/modules/django_drf_theme/templates/rest_framework/tailwind/forms/filter_form.html +132 -0
  298. django_cfg/modules/django_drf_theme/templates/rest_framework/tailwind/forms/raw_data_form.html +123 -0
  299. django_cfg/modules/django_drf_theme/templatetags/__init__.py +1 -0
  300. django_cfg/modules/django_drf_theme/templatetags/tailwind_tags.py +57 -0
  301. django_cfg/modules/django_email/__init__.py +14 -0
  302. django_cfg/modules/{django_email.py → django_email/service.py} +78 -113
  303. django_cfg/modules/django_email/utils.py +40 -0
  304. django_cfg/modules/django_health/__init__.py +9 -0
  305. django_cfg/modules/{django_health.py → django_health/service.py} +23 -21
  306. django_cfg/modules/django_llm/llm/client.py +155 -550
  307. django_cfg/modules/django_llm/llm/embeddings/__init__.py +13 -0
  308. django_cfg/modules/django_llm/llm/embeddings/mock_embedder.py +106 -0
  309. django_cfg/modules/django_llm/llm/embeddings/openai_embedder.py +79 -0
  310. django_cfg/modules/django_llm/llm/models_api/__init__.py +9 -0
  311. django_cfg/modules/django_llm/llm/models_api/models_query.py +163 -0
  312. django_cfg/modules/django_llm/llm/providers/__init__.py +15 -0
  313. django_cfg/modules/django_llm/llm/providers/config_builder.py +103 -0
  314. django_cfg/modules/django_llm/llm/providers/provider_manager.py +148 -0
  315. django_cfg/modules/django_llm/llm/providers/provider_selector.py +60 -0
  316. django_cfg/modules/django_llm/llm/requests/__init__.py +15 -0
  317. django_cfg/modules/django_llm/llm/requests/cache_manager.py +170 -0
  318. django_cfg/modules/django_llm/llm/requests/chat_handler.py +199 -0
  319. django_cfg/modules/django_llm/llm/requests/embedding_handler.py +113 -0
  320. django_cfg/modules/django_llm/llm/responses/__init__.py +9 -0
  321. django_cfg/modules/django_llm/llm/responses/response_builder.py +131 -0
  322. django_cfg/modules/django_llm/llm/stats/__init__.py +9 -0
  323. django_cfg/modules/django_llm/llm/stats/stats_manager.py +107 -0
  324. django_cfg/modules/django_llm/translator/detectors/__init__.py +13 -0
  325. django_cfg/modules/django_llm/translator/detectors/language_detector.py +90 -0
  326. django_cfg/modules/django_llm/translator/detectors/script_detector.py +153 -0
  327. django_cfg/modules/django_llm/translator/stats/__init__.py +11 -0
  328. django_cfg/modules/django_llm/translator/stats/stats_tracker.py +85 -0
  329. django_cfg/modules/django_llm/translator/translator.py +150 -603
  330. django_cfg/modules/django_llm/translator/translators/__init__.py +15 -0
  331. django_cfg/modules/django_llm/translator/translators/json_translator.py +316 -0
  332. django_cfg/modules/django_llm/translator/translators/text_translator.py +139 -0
  333. django_cfg/modules/django_llm/translator/utils/__init__.py +13 -0
  334. django_cfg/modules/django_llm/translator/utils/prompt_builder.py +110 -0
  335. django_cfg/modules/django_llm/translator/utils/text_utils.py +114 -0
  336. django_cfg/modules/django_logging/FIXES_SUMMARY.md +276 -0
  337. django_cfg/modules/django_logging/LOGGING_GUIDE.md +504 -0
  338. django_cfg/modules/django_logging/__init__.py +14 -0
  339. django_cfg/modules/{django_logger.py → django_logging/django_logger.py} +13 -13
  340. django_cfg/modules/{logger.py → django_logging/logger.py} +14 -4
  341. django_cfg/modules/django_ngrok/__init__.py +39 -0
  342. django_cfg/modules/{django_ngrok.py → django_ngrok/service.py} +14 -42
  343. django_cfg/modules/django_rpc_old/POETRY.md +344 -0
  344. django_cfg/modules/django_rpc_old/README.md +397 -0
  345. django_cfg/modules/django_rpc_old/TESTING.md +358 -0
  346. django_cfg/modules/django_rpc_old/__init__.py +39 -0
  347. django_cfg/modules/django_rpc_old/client.py +531 -0
  348. django_cfg/modules/django_rpc_old/config.py +279 -0
  349. django_cfg/modules/django_rpc_old/exceptions.py +172 -0
  350. django_cfg/modules/django_tailwind/README.md +478 -0
  351. django_cfg/modules/django_tailwind/__init__.py +7 -0
  352. django_cfg/modules/django_tailwind/apps.py +10 -0
  353. django_cfg/modules/django_tailwind/templates/django_tailwind/app.html +5 -0
  354. django_cfg/modules/django_tailwind/templates/django_tailwind/base.html +117 -0
  355. django_cfg/modules/django_tailwind/templates/django_tailwind/components/navbar.html +124 -0
  356. django_cfg/modules/django_tailwind/templates/django_tailwind/components/theme_toggle.html +54 -0
  357. django_cfg/modules/django_tailwind/templates/django_tailwind/components/user_menu.html +116 -0
  358. django_cfg/modules/django_tailwind/templates/django_tailwind/simple.html +46 -0
  359. django_cfg/modules/django_tailwind/templatetags/__init__.py +1 -0
  360. django_cfg/modules/django_tailwind/templatetags/tailwind_info.py +185 -0
  361. django_cfg/modules/django_tasks/__init__.py +29 -0
  362. django_cfg/modules/django_tasks/factory.py +127 -0
  363. django_cfg/modules/{django_tasks.py → django_tasks/service.py} +45 -274
  364. django_cfg/modules/django_tasks/settings.py +107 -0
  365. django_cfg/modules/django_telegram/__init__.py +29 -0
  366. django_cfg/modules/{django_telegram.py → django_telegram/service.py} +45 -113
  367. django_cfg/modules/django_telegram/utils.py +62 -0
  368. django_cfg/modules/django_twilio/__init__.py +54 -107
  369. django_cfg/modules/django_twilio/_imports.py +30 -0
  370. django_cfg/modules/django_twilio/base.py +192 -0
  371. django_cfg/modules/django_twilio/email_otp.py +227 -0
  372. django_cfg/modules/django_twilio/sendgrid_service.py +1 -1
  373. django_cfg/modules/django_twilio/simple_service.py +1 -2
  374. django_cfg/modules/django_twilio/sms.py +94 -0
  375. django_cfg/modules/django_twilio/twilio_service.py +2 -3
  376. django_cfg/modules/django_twilio/unified.py +310 -0
  377. django_cfg/modules/django_twilio/utils.py +190 -0
  378. django_cfg/modules/django_twilio/whatsapp.py +137 -0
  379. django_cfg/modules/django_unfold/callbacks/base.py +198 -7
  380. django_cfg/modules/django_unfold/callbacks/main.py +102 -10
  381. django_cfg/modules/django_unfold/dashboard.py +65 -43
  382. django_cfg/modules/django_unfold/models/config.py +13 -12
  383. django_cfg/modules/django_unfold/models/navigation.py +8 -3
  384. django_cfg/modules/django_unfold/models/tabs.py +2 -2
  385. django_cfg/modules/django_unfold/templates/unfold/helpers/app_list.html +102 -0
  386. django_cfg/registry/core.py +24 -26
  387. django_cfg/registry/modules.py +5 -2
  388. django_cfg/registry/services.py +20 -3
  389. django_cfg/registry/third_party.py +8 -8
  390. django_cfg/static/admin/css/dashboard.css +260 -0
  391. django_cfg/static/admin/js/commands.js +171 -0
  392. django_cfg/static/admin/js/dashboard.js +126 -0
  393. django_cfg/templates/admin/components/management_commands.js +375 -0
  394. django_cfg/templates/admin/components/progress_bar.html +18 -23
  395. django_cfg/templates/admin/index.html +48 -20
  396. django_cfg/templates/admin/index_new.html +106 -0
  397. django_cfg/templates/admin/layouts/base_dashboard.html +60 -0
  398. django_cfg/templates/admin/layouts/dashboard_with_tabs.html +1 -20
  399. django_cfg/templates/admin/sections/commands_section.html +626 -0
  400. django_cfg/templates/admin/sections/overview_section.html +112 -0
  401. django_cfg/templates/admin/sections/stats_section.html +35 -0
  402. django_cfg/templates/admin/sections/system_section.html +99 -0
  403. django_cfg/templates/admin/snippets/components/CHARTS_GUIDE.md +322 -0
  404. django_cfg/templates/admin/snippets/components/activity_tracker.html +85 -47
  405. django_cfg/templates/admin/snippets/components/charts_section.html +154 -64
  406. django_cfg/templates/admin/snippets/components/django_commands.html +3 -3
  407. django_cfg/templates/admin/snippets/components/recent_activity_improved.html +25 -0
  408. django_cfg/templates/admin/snippets/components/recent_users_table.html +1 -1
  409. django_cfg/templates/admin/snippets/components/system_metrics.html +179 -93
  410. django_cfg/templates/admin/snippets/zones/zones_table.html +2 -2
  411. django_cfg/templatetags/django_cfg.py +7 -1
  412. django_cfg/utils/smart_defaults.py +4 -4
  413. django_cfg-1.4.0.dist-info/METADATA +920 -0
  414. {django_cfg-1.3.11.dist-info → django_cfg-1.4.0.dist-info}/RECORD +425 -196
  415. django_cfg/apps/accounts/utils/auth_email_service.py +0 -84
  416. django_cfg/apps/payments/services/providers/nowpayments/parsers.py +0 -879
  417. django_cfg/core/generation.py +0 -621
  418. django_cfg/management/commands/validate_config.py +0 -189
  419. django_cfg/models/database.py +0 -480
  420. django_cfg/models/drf.py +0 -272
  421. django_cfg/models/ngrok.py +0 -122
  422. django_cfg/models/services.py +0 -440
  423. django_cfg/models/tasks.py +0 -550
  424. django_cfg/modules/django_twilio/service.py +0 -942
  425. django_cfg/template_archive/django_sample.zip +0 -0
  426. django_cfg/templates/rest_framework/api.html +0 -12
  427. django_cfg/utils/toolkit.py +0 -703
  428. django_cfg-1.3.11.dist-info/METADATA +0 -1029
  429. /django_cfg/apps/accounts/management/commands/{test_otp.py → otp_test.py} +0 -0
  430. /django_cfg/core/{environment.py → environment/detector.py} +0 -0
  431. /django_cfg/models/{cors.py → api/cors.py} +0 -0
  432. /django_cfg/models/{jwt.py → api/jwt.py} +0 -0
  433. /django_cfg/models/{base.py → base/config.py} +0 -0
  434. /django_cfg/models/{cfg.py → base/module.py} +0 -0
  435. /django_cfg/models/{revolution.py → django/revolution.py} +0 -0
  436. /django_cfg/modules/{dramatiq_setup.py → django_tasks/dramatiq_setup.py} +0 -0
  437. {django_cfg-1.3.11.dist-info → django_cfg-1.4.0.dist-info}/WHEEL +0 -0
  438. {django_cfg-1.3.11.dist-info → django_cfg-1.4.0.dist-info}/entry_points.txt +0 -0
  439. {django_cfg-1.3.11.dist-info → django_cfg-1.4.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,942 +0,0 @@
1
- """
2
- Auto-configuring Twilio services for django_cfg.
3
-
4
- This module provides comprehensive OTP and messaging services via WhatsApp, Email, and SMS.
5
- Supports both synchronous and asynchronous operations following Django 5.2+ patterns.
6
-
7
- Following CRITICAL_REQUIREMENTS.md:
8
- - No raw Dict/Any usage - everything through Pydantic models
9
- - Proper type annotations for all fields
10
- - Comprehensive error handling with specific exceptions
11
- - Full async/await support with context detection
12
- """
13
-
14
- import asyncio
15
- import logging
16
- import random
17
- import string
18
- from typing import Optional, Tuple, Dict, Any, List, Union
19
- from datetime import datetime, timedelta
20
- from contextlib import asynccontextmanager
21
-
22
- # Third-party imports
23
- from twilio.rest import Client
24
- from twilio.base.exceptions import TwilioException
25
- from sendgrid import SendGridAPIClient
26
- from sendgrid.helpers.mail import Mail
27
- from asgiref.sync import sync_to_async, async_to_sync
28
-
29
- # Django CFG imports
30
- from django_cfg.modules.base import BaseCfgModule
31
- from django_cfg.modules.django_twilio.models import (
32
- TwilioConfig,
33
- TwilioChannelType,
34
- TwilioVerifyConfig,
35
- SendGridConfig,
36
- )
37
- from django_cfg.modules.django_twilio.exceptions import (
38
- TwilioError,
39
- TwilioConfigurationError,
40
- TwilioVerificationError,
41
- TwilioSendError,
42
- TwilioRateLimitError,
43
- TwilioNetworkError,
44
- )
45
-
46
- logger = logging.getLogger(__name__)
47
-
48
-
49
- def is_async_context() -> bool:
50
- """Detect if running in async context."""
51
- try:
52
- asyncio.get_running_loop()
53
- return True
54
- except RuntimeError:
55
- return False
56
-
57
-
58
- class BaseTwilioService(BaseCfgModule):
59
- """
60
- Base service class for all Twilio operations.
61
-
62
- Provides auto-configuration from DjangoConfig and common utilities
63
- for all Twilio services including error handling and logging.
64
- """
65
-
66
- def __init__(self):
67
- """Initialize with auto-discovered configuration."""
68
- super().__init__()
69
- self._config: Optional[TwilioConfig] = None
70
- self._twilio_client: Optional[Client] = None
71
- self._sendgrid_client: Optional[SendGridAPIClient] = None
72
- self._otp_storage: Dict[str, Dict[str, Any]] = {} # In-memory storage for development
73
-
74
- def get_twilio_config(self) -> TwilioConfig:
75
- """
76
- Get Twilio configuration from DjangoConfig.
77
-
78
- Returns:
79
- TwilioConfig instance
80
-
81
- Raises:
82
- TwilioConfigurationError: If configuration is missing or invalid
83
- """
84
- if self._config is None:
85
- django_config = self.get_config()
86
- if not django_config:
87
- raise TwilioConfigurationError(
88
- "DjangoConfig instance not found",
89
- suggestions=["Ensure DjangoConfig is properly initialized"]
90
- )
91
-
92
- twilio_config = getattr(django_config, 'twilio', None)
93
- if not twilio_config:
94
- raise TwilioConfigurationError(
95
- "Twilio configuration not found in DjangoConfig",
96
- missing_fields=["twilio"],
97
- suggestions=["Add TwilioConfig to your DjangoConfig class"]
98
- )
99
-
100
- self._config = twilio_config
101
-
102
- return self._config
103
-
104
- def get_twilio_client(self) -> Client:
105
- """
106
- Get initialized Twilio client.
107
-
108
- Returns:
109
- Twilio Client instance
110
-
111
- Raises:
112
- TwilioConfigurationError: If client cannot be initialized
113
- """
114
- if self._twilio_client is None:
115
- config = self.get_twilio_config()
116
-
117
- try:
118
- client_config = config.get_client_config()
119
- self._twilio_client = Client(
120
- client_config["username"],
121
- client_config["password"],
122
- region=client_config.get("region")
123
- )
124
-
125
- # Test connection with a simple API call
126
- try:
127
- self._twilio_client.api.v2010.accounts(config.account_sid).fetch()
128
- except TwilioException as e:
129
- raise TwilioConfigurationError(
130
- f"Failed to authenticate with Twilio: {e}",
131
- error_code=getattr(e, 'code', None),
132
- suggestions=[
133
- "Verify TWILIO_ACCOUNT_SID is correct",
134
- "Verify TWILIO_AUTH_TOKEN is correct",
135
- "Check Twilio account status"
136
- ]
137
- ) from e
138
-
139
- except Exception as e:
140
- raise TwilioConfigurationError(
141
- f"Failed to initialize Twilio client: {e}",
142
- suggestions=["Check Twilio configuration parameters"]
143
- ) from e
144
-
145
- return self._twilio_client
146
-
147
- def get_sendgrid_client(self) -> Optional[SendGridAPIClient]:
148
- """
149
- Get initialized SendGrid client.
150
-
151
- Returns:
152
- SendGrid client instance or None if not configured
153
-
154
- Raises:
155
- TwilioConfigurationError: If client cannot be initialized
156
- """
157
- config = self.get_twilio_config()
158
-
159
- if not config.sendgrid:
160
- return None
161
-
162
- if self._sendgrid_client is None:
163
- try:
164
- sendgrid_config = config.get_sendgrid_config()
165
- if sendgrid_config:
166
- self._sendgrid_client = SendGridAPIClient(
167
- api_key=sendgrid_config["api_key"]
168
- )
169
-
170
- except Exception as e:
171
- raise TwilioConfigurationError(
172
- f"Failed to initialize SendGrid client: {e}",
173
- suggestions=["Check SendGrid API key configuration"]
174
- ) from e
175
-
176
- return self._sendgrid_client
177
-
178
- def _generate_otp(self, length: int = 6) -> str:
179
- """Generate numeric OTP code."""
180
- return ''.join(random.choices(string.digits, k=length))
181
-
182
- def _store_otp(self, identifier: str, code: str, ttl_seconds: int = 600) -> None:
183
- """Store OTP code with expiration (in-memory for development)."""
184
- self._otp_storage[identifier] = {
185
- 'code': code,
186
- 'created_at': datetime.now(),
187
- 'expires_at': datetime.now() + timedelta(seconds=ttl_seconds),
188
- 'attempts': 0,
189
- }
190
-
191
- def _get_stored_otp(self, identifier: str) -> Optional[Dict[str, Any]]:
192
- """Get stored OTP data."""
193
- return self._otp_storage.get(identifier)
194
-
195
- def _remove_otp(self, identifier: str) -> None:
196
- """Remove OTP from storage."""
197
- self._otp_storage.pop(identifier, None)
198
-
199
- def _mask_identifier(self, identifier: str) -> str:
200
- """Mask identifier for security in logs."""
201
- if "@" in identifier: # Email
202
- parts = identifier.split("@")
203
- if len(parts) == 2:
204
- return f"{parts[0][:2]}***@{parts[1]}"
205
- else: # Phone number
206
- return f"***{identifier[-4:]}" if len(identifier) > 4 else "***"
207
- return "***"
208
-
209
-
210
- class WhatsAppOTPService(BaseTwilioService):
211
- """
212
- WhatsApp OTP service using Twilio Verify API.
213
-
214
- Provides OTP delivery via WhatsApp with automatic SMS fallback.
215
- Supports both sync and async operations.
216
- """
217
-
218
- def send_otp(self, phone_number: str, fallback_to_sms: bool = True) -> Tuple[bool, str]:
219
- """
220
- Send OTP via WhatsApp with optional SMS fallback.
221
-
222
- Args:
223
- phone_number: Phone number in E.164 format (e.g., +1234567890)
224
- fallback_to_sms: Whether to fallback to SMS if WhatsApp fails
225
-
226
- Returns:
227
- Tuple[bool, str]: (success, message)
228
-
229
- Raises:
230
- TwilioConfigurationError: If service is not configured
231
- TwilioSendError: If sending fails
232
- """
233
- config = self.get_twilio_config()
234
-
235
- if not config.verify:
236
- raise TwilioConfigurationError(
237
- "Twilio Verify service not configured",
238
- missing_fields=["verify"],
239
- suggestions=["Configure TwilioVerifyConfig in your Twilio settings"]
240
- )
241
-
242
- client = self.get_twilio_client()
243
-
244
- try:
245
- # Try WhatsApp first
246
- verification = client.verify.v2.services(
247
- config.verify.service_sid
248
- ).verifications.create(
249
- to=phone_number,
250
- channel='whatsapp'
251
- )
252
-
253
- if verification.status == 'pending':
254
- logger.info(f"WhatsApp OTP sent successfully to {self._mask_identifier(phone_number)}")
255
- return True, f"OTP sent via WhatsApp to {self._mask_identifier(phone_number)}"
256
-
257
- # If WhatsApp failed and fallback is enabled, try SMS
258
- if fallback_to_sms and verification.status != 'pending':
259
- logger.warning(f"WhatsApp failed for {self._mask_identifier(phone_number)}, trying SMS fallback")
260
- return self._send_sms_otp(phone_number, client, config.verify)
261
-
262
- raise TwilioSendError(
263
- f"WhatsApp OTP failed with status: {verification.status}",
264
- channel="whatsapp",
265
- recipient=phone_number,
266
- suggestions=["Check if recipient has WhatsApp Business account", "Try SMS fallback"]
267
- )
268
-
269
- except TwilioException as e:
270
- if fallback_to_sms:
271
- logger.warning(f"WhatsApp error for {self._mask_identifier(phone_number)}: {e}, trying SMS")
272
- return self._send_sms_otp(phone_number, client, config.verify)
273
-
274
- raise TwilioSendError(
275
- f"WhatsApp OTP failed: {e}",
276
- channel="whatsapp",
277
- recipient=phone_number,
278
- twilio_error_code=getattr(e, 'code', None),
279
- twilio_error_message=str(e)
280
- ) from e
281
- except Exception as e:
282
- raise TwilioSendError(
283
- f"Unexpected error sending WhatsApp OTP: {e}",
284
- channel="whatsapp",
285
- recipient=phone_number
286
- ) from e
287
-
288
- async def asend_otp(self, phone_number: str, fallback_to_sms: bool = True) -> Tuple[bool, str]:
289
- """Async version of send_otp."""
290
- return await sync_to_async(self.send_otp)(phone_number, fallback_to_sms)
291
-
292
- def _send_sms_otp(self, phone_number: str, client: Client, verify_config: TwilioVerifyConfig) -> Tuple[bool, str]:
293
- """Internal SMS fallback method."""
294
- try:
295
- verification = client.verify.v2.services(
296
- verify_config.service_sid
297
- ).verifications.create(
298
- to=phone_number,
299
- channel='sms'
300
- )
301
-
302
- if verification.status == 'pending':
303
- logger.info(f"SMS fallback OTP sent to {self._mask_identifier(phone_number)}")
304
- return True, f"OTP sent via SMS to {self._mask_identifier(phone_number)} (WhatsApp fallback)"
305
-
306
- raise TwilioSendError(
307
- f"SMS fallback failed with status: {verification.status}",
308
- channel="sms",
309
- recipient=phone_number
310
- )
311
-
312
- except TwilioException as e:
313
- raise TwilioSendError(
314
- f"SMS fallback failed: {e}",
315
- channel="sms",
316
- recipient=phone_number,
317
- twilio_error_code=getattr(e, 'code', None),
318
- twilio_error_message=str(e)
319
- ) from e
320
-
321
-
322
- class EmailOTPService(BaseTwilioService):
323
- """
324
- Email OTP service using SendGrid.
325
-
326
- Provides OTP delivery via email with template support and
327
- comprehensive deliverability optimization.
328
- """
329
-
330
- def send_otp(
331
- self,
332
- email: str,
333
- subject: Optional[str] = None,
334
- template_data: Optional[Dict[str, Any]] = None
335
- ) -> Tuple[bool, str, str]:
336
- """
337
- Send OTP via email.
338
-
339
- Args:
340
- email: Recipient email address
341
- subject: Custom email subject (uses default if not provided)
342
- template_data: Additional data for email template
343
-
344
- Returns:
345
- Tuple[bool, str, str]: (success, message, otp_code)
346
-
347
- Raises:
348
- TwilioConfigurationError: If SendGrid is not configured
349
- TwilioSendError: If email sending fails
350
- """
351
- config = self.get_twilio_config()
352
-
353
- if not config.sendgrid:
354
- raise TwilioConfigurationError(
355
- "SendGrid configuration not found",
356
- missing_fields=["sendgrid"],
357
- suggestions=["Configure SendGridConfig in your Twilio settings"]
358
- )
359
-
360
- sendgrid_client = self.get_sendgrid_client()
361
- if not sendgrid_client:
362
- raise TwilioConfigurationError("SendGrid client not initialized")
363
-
364
- try:
365
- # Generate OTP code
366
- otp_code = self._generate_otp(6)
367
-
368
- # Store OTP for verification
369
- self._store_otp(email, otp_code, config.verify.ttl_seconds if config.verify else 600)
370
-
371
- # Prepare email content
372
- if config.sendgrid.otp_template_id:
373
- # Use dynamic template
374
- success, message = self._send_template_email(
375
- sendgrid_client, config.sendgrid, email, otp_code, template_data
376
- )
377
- else:
378
- # Use simple HTML email
379
- success, message = self._send_simple_email(
380
- sendgrid_client, config.sendgrid, email, otp_code, subject
381
- )
382
-
383
- if success:
384
- logger.info(f"Email OTP sent successfully to {self._mask_identifier(email)}")
385
- return True, message, otp_code
386
- else:
387
- raise TwilioSendError(message, channel="email", recipient=email)
388
-
389
- except Exception as e:
390
- if isinstance(e, TwilioSendError):
391
- raise
392
- raise TwilioSendError(
393
- f"Failed to send email OTP: {e}",
394
- channel="email",
395
- recipient=email
396
- ) from e
397
-
398
- async def asend_otp(
399
- self,
400
- email: str,
401
- subject: Optional[str] = None,
402
- template_data: Optional[Dict[str, Any]] = None
403
- ) -> Tuple[bool, str, str]:
404
- """Async version of send_otp."""
405
- return await sync_to_async(self.send_otp)(email, subject, template_data)
406
-
407
- def _send_template_email(
408
- self,
409
- client: SendGridAPIClient,
410
- config: SendGridConfig,
411
- email: str,
412
- otp_code: str,
413
- template_data: Optional[Dict[str, Any]] = None
414
- ) -> Tuple[bool, str]:
415
- """Send email using SendGrid dynamic template."""
416
- try:
417
- # Prepare template data
418
- dynamic_data = {
419
- 'verification_code': otp_code,
420
- 'user_email': email,
421
- 'expiry_minutes': 10,
422
- 'company_name': config.from_name,
423
- **config.custom_template_data,
424
- **(template_data or {})
425
- }
426
-
427
- message = Mail(
428
- from_email=(config.from_email, config.from_name),
429
- to_emails=email
430
- )
431
-
432
- message.template_id = config.otp_template_id
433
- message.dynamic_template_data = dynamic_data
434
-
435
- if config.reply_to_email:
436
- message.reply_to = config.reply_to_email
437
-
438
- response = client.send(message)
439
-
440
- if response.status_code in [200, 201, 202]:
441
- return True, f"OTP sent via email template to {self._mask_identifier(email)}"
442
- else:
443
- return False, f"SendGrid API error: {response.status_code}"
444
-
445
- except Exception as e:
446
- return False, f"Template email error: {e}"
447
-
448
- def _send_simple_email(
449
- self,
450
- client: SendGridAPIClient,
451
- config: SendGridConfig,
452
- email: str,
453
- otp_code: str,
454
- subject: Optional[str] = None
455
- ) -> Tuple[bool, str]:
456
- """Send simple HTML email without template."""
457
- try:
458
- html_content = self._generate_html_content(otp_code, config.from_name)
459
- plain_content = self._generate_plain_content(otp_code)
460
-
461
- message = Mail(
462
- from_email=(config.from_email, config.from_name),
463
- to_emails=email,
464
- subject=subject or config.default_subject,
465
- html_content=html_content,
466
- plain_text_content=plain_content
467
- )
468
-
469
- if config.reply_to_email:
470
- message.reply_to = config.reply_to_email
471
-
472
- response = client.send(message)
473
-
474
- if response.status_code in [200, 201, 202]:
475
- return True, f"OTP sent via email to {self._mask_identifier(email)}"
476
- else:
477
- return False, f"SendGrid API error: {response.status_code}"
478
-
479
- except Exception as e:
480
- return False, f"Simple email error: {e}"
481
-
482
- def _generate_html_content(self, otp_code: str, company_name: str) -> str:
483
- """Generate HTML email content."""
484
- return f"""
485
- <div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px;">
486
- <div style="background-color: #f8f9fa; padding: 30px; border-radius: 10px; text-align: center;">
487
- <h1 style="color: #333; margin-bottom: 20px;">Verification Code</h1>
488
- <p style="color: #666; font-size: 16px; margin-bottom: 30px;">
489
- Your verification code is:
490
- </p>
491
- <div style="background-color: #007bff; color: white; font-size: 32px; font-weight: bold;
492
- padding: 20px; border-radius: 8px; letter-spacing: 5px; margin: 30px 0;">
493
- {otp_code}
494
- </div>
495
- <p style="color: #999; font-size: 14px;">
496
- This code expires in 10 minutes<br>
497
- If you didn't request this code, please ignore this email
498
- </p>
499
- <hr style="border: none; border-top: 1px solid #eee; margin: 30px 0;">
500
- <p style="color: #999; font-size: 12px;">
501
- Sent by {company_name}
502
- </p>
503
- </div>
504
- </div>
505
- """
506
-
507
- def _generate_plain_content(self, otp_code: str) -> str:
508
- """Generate plain text email content."""
509
- return f"""
510
- Your verification code: {otp_code}
511
-
512
- This code expires in 10 minutes.
513
- If you didn't request this code, please ignore this email.
514
- """.strip()
515
-
516
-
517
- class SMSOTPService(BaseTwilioService):
518
- """
519
- SMS OTP service using Twilio Verify API.
520
-
521
- Provides reliable SMS OTP delivery with comprehensive
522
- error handling and international support.
523
- """
524
-
525
- def send_otp(self, phone_number: str) -> Tuple[bool, str]:
526
- """
527
- Send OTP via SMS.
528
-
529
- Args:
530
- phone_number: Phone number in E.164 format
531
-
532
- Returns:
533
- Tuple[bool, str]: (success, message)
534
-
535
- Raises:
536
- TwilioConfigurationError: If Verify service not configured
537
- TwilioSendError: If SMS sending fails
538
- """
539
- config = self.get_twilio_config()
540
-
541
- if not config.verify:
542
- raise TwilioConfigurationError(
543
- "Twilio Verify service not configured",
544
- missing_fields=["verify"],
545
- suggestions=["Configure TwilioVerifyConfig in your Twilio settings"]
546
- )
547
-
548
- client = self.get_twilio_client()
549
-
550
- try:
551
- verification = client.verify.v2.services(
552
- config.verify.service_sid
553
- ).verifications.create(
554
- to=phone_number,
555
- channel='sms'
556
- )
557
-
558
- if verification.status == 'pending':
559
- logger.info(f"SMS OTP sent successfully to {self._mask_identifier(phone_number)}")
560
- return True, f"OTP sent via SMS to {self._mask_identifier(phone_number)}"
561
- else:
562
- raise TwilioSendError(
563
- f"SMS OTP failed with status: {verification.status}",
564
- channel="sms",
565
- recipient=phone_number
566
- )
567
-
568
- except TwilioException as e:
569
- raise TwilioSendError(
570
- f"SMS OTP failed: {e}",
571
- channel="sms",
572
- recipient=phone_number,
573
- twilio_error_code=getattr(e, 'code', None),
574
- twilio_error_message=str(e)
575
- ) from e
576
- except Exception as e:
577
- raise TwilioSendError(
578
- f"Unexpected error sending SMS OTP: {e}",
579
- channel="sms",
580
- recipient=phone_number
581
- ) from e
582
-
583
- async def asend_otp(self, phone_number: str) -> Tuple[bool, str]:
584
- """Async version of send_otp."""
585
- return await sync_to_async(self.send_otp)(phone_number)
586
-
587
-
588
- class UnifiedOTPService(BaseTwilioService):
589
- """
590
- Unified OTP service that handles all channels with smart fallbacks.
591
-
592
- Provides intelligent channel selection and automatic fallback
593
- based on configuration and delivery success rates.
594
- """
595
-
596
- def __init__(self):
597
- """Initialize with specialized service instances."""
598
- super().__init__()
599
- self._whatsapp_service = WhatsAppOTPService()
600
- self._email_service = EmailOTPService()
601
- self._sms_service = SMSOTPService()
602
-
603
- def send_otp(
604
- self,
605
- identifier: str,
606
- preferred_channel: Optional[TwilioChannelType] = None,
607
- enable_fallback: bool = True
608
- ) -> Tuple[bool, str, TwilioChannelType]:
609
- """
610
- Send OTP using the best available channel.
611
-
612
- Args:
613
- identifier: Phone number (E.164) or email address
614
- preferred_channel: Preferred delivery channel
615
- enable_fallback: Whether to try fallback channels
616
-
617
- Returns:
618
- Tuple[bool, str, TwilioChannelType]: (success, message, used_channel)
619
- """
620
- config = self.get_twilio_config()
621
-
622
- # Determine identifier type
623
- is_email = "@" in identifier
624
-
625
- # Get available channels
626
- available_channels = self._get_available_channels(is_email, config)
627
-
628
- if not available_channels:
629
- raise TwilioConfigurationError(
630
- "No channels configured for OTP delivery",
631
- suggestions=["Configure at least one channel (WhatsApp, SMS, or Email)"]
632
- )
633
-
634
- # Determine channel order
635
- channel_order = self._get_channel_order(
636
- available_channels, preferred_channel, is_email, config
637
- )
638
-
639
- last_error = None
640
-
641
- for channel in channel_order:
642
- try:
643
- success, message = self._send_via_channel(identifier, channel)
644
- if success:
645
- return True, message, channel
646
-
647
- except Exception as e:
648
- last_error = e
649
- logger.warning(f"Channel {channel.value} failed for {self._mask_identifier(identifier)}: {e}")
650
-
651
- if not enable_fallback:
652
- raise
653
-
654
- # All channels failed
655
- raise TwilioSendError(
656
- f"All configured channels failed for {self._mask_identifier(identifier)}",
657
- context={"tried_channels": [ch.value for ch in channel_order]},
658
- suggestions=["Check service configurations", "Verify recipient details"]
659
- ) from last_error
660
-
661
- async def asend_otp(
662
- self,
663
- identifier: str,
664
- preferred_channel: Optional[TwilioChannelType] = None,
665
- enable_fallback: bool = True
666
- ) -> Tuple[bool, str, TwilioChannelType]:
667
- """Async version of send_otp."""
668
- return await sync_to_async(self.send_otp)(identifier, preferred_channel, enable_fallback)
669
-
670
- def verify_otp(self, identifier: str, code: str) -> Tuple[bool, str]:
671
- """
672
- Verify OTP code for any channel.
673
-
674
- Args:
675
- identifier: Phone number or email used for OTP
676
- code: OTP code to verify
677
-
678
- Returns:
679
- Tuple[bool, str]: (is_valid, message)
680
- """
681
- config = self.get_twilio_config()
682
-
683
- # For Twilio Verify channels (WhatsApp, SMS), use Twilio verification
684
- if not "@" in identifier and config.verify:
685
- return self._verify_twilio_otp(identifier, code, config)
686
-
687
- # For email or custom verification, use stored OTP
688
- return self._verify_stored_otp(identifier, code)
689
-
690
- async def averify_otp(self, identifier: str, code: str) -> Tuple[bool, str]:
691
- """Async version of verify_otp."""
692
- return await sync_to_async(self.verify_otp)(identifier, code)
693
-
694
- def _get_available_channels(self, is_email: bool, config: TwilioConfig) -> List[TwilioChannelType]:
695
- """Get list of available channels based on configuration."""
696
- channels = []
697
-
698
- if config.verify:
699
- if not is_email: # Phone number - can use WhatsApp/SMS
700
- channels.extend([TwilioChannelType.WHATSAPP, TwilioChannelType.SMS])
701
-
702
- if config.sendgrid: # Email available
703
- channels.append(TwilioChannelType.EMAIL)
704
-
705
- return channels
706
-
707
- def _get_channel_order(
708
- self,
709
- available_channels: List[TwilioChannelType],
710
- preferred_channel: Optional[TwilioChannelType],
711
- is_email: bool,
712
- config: TwilioConfig
713
- ) -> List[TwilioChannelType]:
714
- """Determine optimal channel order for delivery attempts."""
715
-
716
- # If preferred channel is specified and available, try it first
717
- if preferred_channel and preferred_channel in available_channels:
718
- ordered_channels = [preferred_channel]
719
- remaining = [ch for ch in available_channels if ch != preferred_channel]
720
- ordered_channels.extend(remaining)
721
- return ordered_channels
722
-
723
- # Default ordering based on identifier type and configuration
724
- if is_email:
725
- return [TwilioChannelType.EMAIL]
726
-
727
- # For phone numbers, prefer WhatsApp -> SMS
728
- phone_channels = []
729
- if TwilioChannelType.WHATSAPP in available_channels:
730
- phone_channels.append(TwilioChannelType.WHATSAPP)
731
- if TwilioChannelType.SMS in available_channels:
732
- phone_channels.append(TwilioChannelType.SMS)
733
-
734
- return phone_channels
735
-
736
- def _send_via_channel(self, identifier: str, channel: TwilioChannelType) -> Tuple[bool, str]:
737
- """Send OTP via specific channel."""
738
- if channel == TwilioChannelType.WHATSAPP:
739
- return self._whatsapp_service.send_otp(identifier, fallback_to_sms=False)
740
- elif channel == TwilioChannelType.SMS:
741
- return self._sms_service.send_otp(identifier)
742
- elif channel == TwilioChannelType.EMAIL:
743
- success, message, _ = self._email_service.send_otp(identifier)
744
- return success, message
745
- else:
746
- raise TwilioSendError(f"Unsupported channel: {channel.value}")
747
-
748
- def _verify_twilio_otp(self, phone_number: str, code: str, config: TwilioConfig) -> Tuple[bool, str]:
749
- """Verify OTP using Twilio Verify API."""
750
- try:
751
- client = self.get_twilio_client()
752
-
753
- verification_check = client.verify.v2.services(
754
- config.verify.service_sid
755
- ).verification_checks.create(
756
- to=phone_number,
757
- code=code
758
- )
759
-
760
- if verification_check.status == 'approved':
761
- logger.info(f"OTP verified successfully for {self._mask_identifier(phone_number)}")
762
- return True, "OTP verified successfully"
763
- else:
764
- return False, f"Invalid OTP code: {verification_check.status}"
765
-
766
- except TwilioException as e:
767
- raise TwilioVerificationError(
768
- f"OTP verification failed: {e}",
769
- phone_number=phone_number,
770
- twilio_error_code=getattr(e, 'code', None),
771
- twilio_error_message=str(e)
772
- ) from e
773
-
774
- def _verify_stored_otp(self, identifier: str, code: str) -> Tuple[bool, str]:
775
- """Verify OTP using stored codes (for email)."""
776
- stored_data = self._get_stored_otp(identifier)
777
-
778
- if not stored_data:
779
- return False, "OTP not found. Please request a new code."
780
-
781
- if datetime.now() > stored_data['expires_at']:
782
- self._remove_otp(identifier)
783
- return False, "OTP expired. Please request a new code."
784
-
785
- # Increment attempt counter
786
- stored_data['attempts'] += 1
787
-
788
- if stored_data['attempts'] > 5: # Max attempts
789
- self._remove_otp(identifier)
790
- return False, "Too many attempts. Please request a new code."
791
-
792
- if stored_data['code'] == code:
793
- self._remove_otp(identifier)
794
- logger.info(f"Stored OTP verified successfully for {self._mask_identifier(identifier)}")
795
- return True, "OTP verified successfully"
796
- else:
797
- return False, f"Invalid OTP code. {5 - stored_data['attempts']} attempts remaining."
798
-
799
-
800
- class DjangoTwilioService(UnifiedOTPService):
801
- """
802
- Main Twilio service for django_cfg integration.
803
-
804
- Provides unified access to all Twilio services with auto-configuration
805
- and comprehensive error handling. This is the primary service class
806
- that should be used in most applications.
807
- """
808
-
809
- def __init__(self):
810
- """Initialize with all service capabilities."""
811
- super().__init__()
812
- logger.info("DjangoTwilioService initialized with auto-configuration")
813
-
814
- def get_service_status(self) -> Dict[str, Any]:
815
- """
816
- Get comprehensive status of all Twilio services.
817
-
818
- Returns:
819
- Dictionary with service status information
820
- """
821
- try:
822
- config = self.get_twilio_config()
823
-
824
- status = {
825
- "twilio_configured": True,
826
- "account_sid": config.account_sid,
827
- "region": config.region.value,
828
- "services": {},
829
- "enabled_channels": [ch.value for ch in config.get_enabled_channels()],
830
- "test_mode": config.test_mode,
831
- }
832
-
833
- # Check Verify service
834
- if config.verify:
835
- status["services"]["verify"] = {
836
- "enabled": True,
837
- "service_sid": config.verify.service_sid,
838
- "default_channel": config.verify.default_channel.value,
839
- "fallback_channels": [ch.value for ch in config.verify.fallback_channels],
840
- "code_length": config.verify.code_length,
841
- "ttl_seconds": config.verify.ttl_seconds,
842
- }
843
- else:
844
- status["services"]["verify"] = {"enabled": False}
845
-
846
- # Check SendGrid service
847
- if config.sendgrid:
848
- status["services"]["sendgrid"] = {
849
- "enabled": True,
850
- "from_email": config.sendgrid.from_email,
851
- "from_name": config.sendgrid.from_name,
852
- "template_configured": config.sendgrid.otp_template_id is not None,
853
- "tracking_enabled": config.sendgrid.tracking_enabled,
854
- }
855
- else:
856
- status["services"]["sendgrid"] = {"enabled": False}
857
-
858
- return status
859
-
860
- except Exception as e:
861
- return {
862
- "twilio_configured": False,
863
- "error": str(e),
864
- "services": {},
865
- }
866
-
867
-
868
- # Convenience functions for direct usage
869
- def send_whatsapp_otp(phone_number: str, fallback_to_sms: bool = True) -> Tuple[bool, str]:
870
- """Send WhatsApp OTP with optional SMS fallback."""
871
- service = WhatsAppOTPService()
872
- return service.send_otp(phone_number, fallback_to_sms)
873
-
874
-
875
- def send_email_otp(email: str, subject: Optional[str] = None) -> Tuple[bool, str, str]:
876
- """Send email OTP."""
877
- service = EmailOTPService()
878
- return service.send_otp(email, subject)
879
-
880
-
881
- def send_sms_otp(phone_number: str) -> Tuple[bool, str]:
882
- """Send SMS OTP."""
883
- service = SMSOTPService()
884
- return service.send_otp(phone_number)
885
-
886
-
887
- def verify_otp(identifier: str, code: str) -> Tuple[bool, str]:
888
- """Verify OTP code for any channel."""
889
- service = UnifiedOTPService()
890
- return service.verify_otp(identifier, code)
891
-
892
-
893
- # Async convenience functions
894
- async def asend_whatsapp_otp(phone_number: str, fallback_to_sms: bool = True) -> Tuple[bool, str]:
895
- """Async send WhatsApp OTP."""
896
- service = WhatsAppOTPService()
897
- return await service.asend_otp(phone_number, fallback_to_sms)
898
-
899
-
900
- async def asend_email_otp(email: str, subject: Optional[str] = None) -> Tuple[bool, str, str]:
901
- """Async send email OTP."""
902
- service = EmailOTPService()
903
- return await service.asend_otp(email, subject)
904
-
905
-
906
- async def asend_sms_otp(phone_number: str) -> Tuple[bool, str]:
907
- """Async send SMS OTP."""
908
- service = SMSOTPService()
909
- return await service.asend_otp(phone_number)
910
-
911
-
912
- async def averify_otp(identifier: str, code: str) -> Tuple[bool, str]:
913
- """Async verify OTP code."""
914
- service = UnifiedOTPService()
915
- return await service.averify_otp(identifier, code)
916
-
917
-
918
- # Export all service classes and functions
919
- __all__ = [
920
- # Service classes
921
- "DjangoTwilioService",
922
- "WhatsAppOTPService",
923
- "EmailOTPService",
924
- "SMSOTPService",
925
- "UnifiedOTPService",
926
- "BaseTwilioService",
927
-
928
- # Sync convenience functions
929
- "send_whatsapp_otp",
930
- "send_email_otp",
931
- "send_sms_otp",
932
- "verify_otp",
933
-
934
- # Async convenience functions
935
- "asend_whatsapp_otp",
936
- "asend_email_otp",
937
- "asend_sms_otp",
938
- "averify_otp",
939
-
940
- # Utility functions
941
- "is_async_context",
942
- ]