django-cfg 1.1.49__tar.gz → 1.1.51__tar.gz

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 (248) hide show
  1. {django_cfg-1.1.49 → django_cfg-1.1.51}/.gitignore +2 -0
  2. {django_cfg-1.1.49 → django_cfg-1.1.51}/LICENSE +1 -1
  3. {django_cfg-1.1.49 → django_cfg-1.1.51}/PKG-INFO +4 -4
  4. {django_cfg-1.1.49 → django_cfg-1.1.51}/README.md +1 -1
  5. {django_cfg-1.1.49 → django_cfg-1.1.51}/pyproject.toml +3 -3
  6. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/__init__.py +3 -3
  7. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/accounts/admin/__init__.py +2 -0
  8. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/accounts/admin/filters.py +54 -0
  9. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/accounts/admin/otp.py +12 -1
  10. django_cfg-1.1.51/src/django_cfg/apps/accounts/admin/twilio_response.py +222 -0
  11. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/accounts/managers/user_manager.py +16 -0
  12. django_cfg-1.1.51/src/django_cfg/apps/accounts/migrations/0003_twilioresponse.py +43 -0
  13. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/accounts/models.py +93 -0
  14. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/accounts/serializers/otp.py +2 -2
  15. django_cfg-1.1.51/src/django_cfg/apps/accounts/serializers/webhook.py +94 -0
  16. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/accounts/services/otp_service.py +117 -25
  17. django_cfg-1.1.51/src/django_cfg/apps/accounts/templates/emails/otp_email.html +213 -0
  18. django_cfg-1.1.51/src/django_cfg/apps/accounts/templates/emails/otp_email.txt +29 -0
  19. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/accounts/urls.py +2 -1
  20. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/accounts/utils/notifications.py +184 -32
  21. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/accounts/views/__init__.py +3 -0
  22. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/accounts/views/otp.py +8 -0
  23. django_cfg-1.1.51/src/django_cfg/apps/accounts/views/webhook.py +265 -0
  24. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/api/commands/urls.py +2 -0
  25. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/api/health/urls.py +2 -0
  26. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/tasks/urls.py +1 -1
  27. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/archive/django_sample.zip +0 -0
  28. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/cli/README.md +1 -1
  29. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/cli/commands/create_project.py +2 -2
  30. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/cli/commands/info.py +1 -1
  31. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/core/config.py +6 -0
  32. django_cfg-1.1.51/src/django_cfg/management/commands/list_urls.py +302 -0
  33. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/management/commands/rundramatiq.py +2 -1
  34. django_cfg-1.1.51/src/django_cfg/management/commands/show_urls.py +302 -0
  35. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/management/commands/test_email.py +1 -1
  36. django_cfg-1.1.51/src/django_cfg/management/commands/test_twilio.py +614 -0
  37. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/modules/django_llm/llm/client.py +2 -2
  38. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/modules/django_twilio/README.md +1 -1
  39. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/modules/django_twilio/__init__.py +46 -38
  40. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/modules/django_twilio/models.py +34 -16
  41. django_cfg-1.1.51/src/django_cfg/modules/django_twilio/sendgrid_service.py +504 -0
  42. django_cfg-1.1.51/src/django_cfg/modules/django_twilio/templates/guide.md +266 -0
  43. django_cfg-1.1.51/src/django_cfg/modules/django_twilio/templates/sendgrid_otp_email.html +213 -0
  44. django_cfg-1.1.51/src/django_cfg/modules/django_twilio/templates/sendgrid_test_data.json +14 -0
  45. django_cfg-1.1.51/src/django_cfg/modules/django_twilio/twilio_service.py +472 -0
  46. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/modules/unfold/dashboard.py +45 -1
  47. django_cfg-1.1.49/src/django_cfg/apps/accounts/templates/emails/otp_email.html +0 -94
  48. django_cfg-1.1.49/src/django_cfg/apps/accounts/templates/emails/otp_email.txt +0 -16
  49. django_cfg-1.1.49/src/django_cfg/examples/README_NGROK.md +0 -186
  50. django_cfg-1.1.49/src/django_cfg/examples/README_NGROK_ENV.md +0 -194
  51. django_cfg-1.1.49/src/django_cfg/examples/ngrok_env_example.py +0 -155
  52. django_cfg-1.1.49/src/django_cfg/examples/ngrok_example.py +0 -75
  53. django_cfg-1.1.49/src/django_cfg/management/commands/show_urls.py +0 -341
  54. django_cfg-1.1.49/src/django_cfg/management/commands/test_twilio.py +0 -101
  55. django_cfg-1.1.49/src/django_cfg/modules/django_twilio/service.py +0 -942
  56. django_cfg-1.1.49/src/django_cfg/modules/django_twilio/simple_service.py +0 -290
  57. {django_cfg-1.1.49 → django_cfg-1.1.51}/MANIFEST.in +0 -0
  58. {django_cfg-1.1.49 → django_cfg-1.1.51}/requirements-dev.txt +0 -0
  59. {django_cfg-1.1.49 → django_cfg-1.1.51}/requirements-test.txt +0 -0
  60. {django_cfg-1.1.49 → django_cfg-1.1.51}/requirements.txt +0 -0
  61. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/__init__.py +0 -0
  62. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/accounts/README.md +0 -0
  63. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/accounts/__init__.py +0 -0
  64. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/accounts/admin/activity.py +0 -0
  65. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/accounts/admin/group.py +0 -0
  66. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/accounts/admin/inlines.py +0 -0
  67. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/accounts/admin/registration_source.py +0 -0
  68. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/accounts/admin/user.py +0 -0
  69. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/accounts/apps.py +0 -0
  70. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/accounts/management/commands/test_otp.py +0 -0
  71. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/accounts/managers/__init__.py +0 -0
  72. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/accounts/migrations/0001_initial.py +0 -0
  73. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/accounts/migrations/0002_add_phone_otp_clean.py +0 -0
  74. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/accounts/migrations/__init__.py +0 -0
  75. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/accounts/serializers/__init__.py +0 -0
  76. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/accounts/serializers/profile.py +0 -0
  77. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/accounts/services/__init__.py +0 -0
  78. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/accounts/services/activity_service.py +0 -0
  79. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/accounts/signals.py +0 -0
  80. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/accounts/templates/emails/base_email.html +0 -0
  81. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/accounts/templates/emails/base_email.txt +0 -0
  82. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/accounts/templates/emails/welcome_email.html +0 -0
  83. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/accounts/templates/emails/welcome_email.txt +0 -0
  84. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/accounts/views/profile.py +0 -0
  85. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/api/__init__.py +0 -0
  86. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/api/commands/__init__.py +0 -0
  87. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/api/commands/views.py +0 -0
  88. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/api/health/__init__.py +0 -0
  89. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/api/health/views.py +0 -0
  90. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/leads/README.md +0 -0
  91. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/leads/__init__.py +0 -0
  92. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/leads/admin.py +0 -0
  93. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/leads/apps.py +0 -0
  94. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/leads/migrations/0001_initial.py +0 -0
  95. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/leads/migrations/__init__.py +0 -0
  96. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/leads/models.py +0 -0
  97. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/leads/serializers.py +0 -0
  98. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/leads/signals.py +0 -0
  99. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/leads/tests.py +0 -0
  100. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/leads/urls.py +0 -0
  101. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/leads/views.py +0 -0
  102. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/newsletter/README.md +0 -0
  103. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/newsletter/__init__.py +0 -0
  104. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/newsletter/admin.py +0 -0
  105. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/newsletter/admin_filters.py +0 -0
  106. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/newsletter/apps.py +0 -0
  107. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/newsletter/management/__init__.py +0 -0
  108. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/newsletter/management/commands/__init__.py +0 -0
  109. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/newsletter/management/commands/test_newsletter.py +0 -0
  110. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/newsletter/managers/README.md +0 -0
  111. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/newsletter/managers/__init__.py +0 -0
  112. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/newsletter/migrations/0001_initial.py +0 -0
  113. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/newsletter/migrations/__init__.py +0 -0
  114. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/newsletter/models.py +0 -0
  115. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/newsletter/serializers.py +0 -0
  116. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/newsletter/services/email_service.py +0 -0
  117. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/newsletter/signals.py +0 -0
  118. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/newsletter/urls.py +0 -0
  119. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/newsletter/utils/__init__.py +0 -0
  120. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/newsletter/views/__init__.py +0 -0
  121. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/newsletter/views/campaigns.py +0 -0
  122. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/newsletter/views/emails.py +0 -0
  123. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/newsletter/views/newsletters.py +0 -0
  124. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/newsletter/views/subscriptions.py +0 -0
  125. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/newsletter/views/tracking.py +0 -0
  126. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/support/__init__.py +0 -0
  127. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/support/admin.py +0 -0
  128. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/support/admin_filters.py +0 -0
  129. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/support/apps.py +0 -0
  130. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/support/managers/message_manager.py +0 -0
  131. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/support/managers/ticket_manager.py +0 -0
  132. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/support/migrations/0001_initial.py +0 -0
  133. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/support/migrations/0002_alter_message_ticket.py +0 -0
  134. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/support/migrations/__init__.py +0 -0
  135. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/support/models.py +0 -0
  136. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/support/serializers.py +0 -0
  137. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/support/signals.py +0 -0
  138. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/support/templates/support/chat/access_denied.html +0 -0
  139. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/support/templates/support/chat/ticket_chat.html +0 -0
  140. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/support/urls.py +0 -0
  141. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/support/utils/__init__.py +0 -0
  142. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/support/utils/support_email_service.py +0 -0
  143. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/support/views/__init__.py +0 -0
  144. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/support/views/admin.py +0 -0
  145. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/support/views/api.py +0 -0
  146. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/support/views/chat.py +0 -0
  147. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/tasks/__init__.py +0 -0
  148. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/tasks/admin.py +0 -0
  149. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/tasks/apps.py +0 -0
  150. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/tasks/templates/tasks/dashboard.html +0 -0
  151. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/tasks/views.py +0 -0
  152. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps/urls.py +0 -0
  153. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/apps.py +0 -0
  154. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/cli/__init__.py +0 -0
  155. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/cli/commands/__init__.py +0 -0
  156. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/cli/main.py +0 -0
  157. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/cli/utils.py +0 -0
  158. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/core/__init__.py +0 -0
  159. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/core/environment.py +0 -0
  160. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/core/generation.py +0 -0
  161. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/core/validation.py +0 -0
  162. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/exceptions.py +0 -0
  163. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/integration.py +0 -0
  164. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/management/__init__.py +0 -0
  165. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/management/commands/__init__.py +0 -0
  166. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/management/commands/check_settings.py +0 -0
  167. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/management/commands/create_token.py +0 -0
  168. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/management/commands/generate.py +0 -0
  169. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/management/commands/migrator.py +0 -0
  170. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/management/commands/runserver_ngrok.py +0 -0
  171. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/management/commands/script.py +0 -0
  172. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/management/commands/show_config.py +0 -0
  173. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/management/commands/superuser.py +0 -0
  174. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/management/commands/task_clear.py +0 -0
  175. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/management/commands/task_status.py +0 -0
  176. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/management/commands/test_telegram.py +0 -0
  177. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/management/commands/tree.py +0 -0
  178. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/management/commands/validate_config.py +0 -0
  179. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/middleware/README.md +0 -0
  180. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/middleware/__init__.py +0 -0
  181. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/middleware/user_activity.py +0 -0
  182. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/models/__init__.py +0 -0
  183. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/models/cache.py +0 -0
  184. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/models/constance.py +0 -0
  185. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/models/database.py +0 -0
  186. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/models/drf.py +0 -0
  187. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/models/jwt.py +0 -0
  188. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/models/limits.py +0 -0
  189. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/models/ngrok.py +0 -0
  190. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/models/revolution.py +0 -0
  191. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/models/services.py +0 -0
  192. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/models/tasks.py +0 -0
  193. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/models/unfold.py +0 -0
  194. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/modules/__init__.py +0 -0
  195. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/modules/base.py +0 -0
  196. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/modules/django_currency/README.md +0 -0
  197. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/modules/django_currency/__init__.py +0 -0
  198. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/modules/django_currency/cache.py +0 -0
  199. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/modules/django_currency/converter.py +0 -0
  200. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/modules/django_currency/service.py +0 -0
  201. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/modules/django_email.py +0 -0
  202. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/modules/django_llm/README.md +0 -0
  203. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/modules/django_llm/__init__.py +0 -0
  204. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/modules/django_llm/example.py +0 -0
  205. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/modules/django_llm/llm/__init__.py +0 -0
  206. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/modules/django_llm/llm/cache.py +0 -0
  207. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/modules/django_llm/llm/models_cache.py +0 -0
  208. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/modules/django_llm/service.py +0 -0
  209. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/modules/django_llm/translator/__init__.py +0 -0
  210. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/modules/django_llm/translator/cache.py +0 -0
  211. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/modules/django_llm/translator/translator.py +0 -0
  212. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/modules/django_logger.py +0 -0
  213. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/modules/django_ngrok.py +0 -0
  214. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/modules/django_tasks.py +0 -0
  215. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/modules/django_telegram.py +0 -0
  216. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/modules/django_twilio/exceptions.py +0 -0
  217. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/modules/logger.py +0 -0
  218. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/modules/unfold/__init__.py +0 -0
  219. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/modules/unfold/callbacks.py +0 -0
  220. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/modules/unfold/models.py +0 -0
  221. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/modules/unfold/system_monitor.py +0 -0
  222. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/modules/unfold/tailwind.py +0 -0
  223. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/routers.py +0 -0
  224. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/templates/__init__.py +0 -0
  225. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/templates/admin/index.html +0 -0
  226. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/templates/admin/layouts/dashboard_with_tabs.html +0 -0
  227. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/templates/admin/snippets/components/activity_tracker.html +0 -0
  228. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/templates/admin/snippets/components/charts_section.html +0 -0
  229. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/templates/admin/snippets/components/django_commands.html +0 -0
  230. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/templates/admin/snippets/components/quick_actions.html +0 -0
  231. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/templates/admin/snippets/components/recent_activity.html +0 -0
  232. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/templates/admin/snippets/components/recent_users_table.html +0 -0
  233. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/templates/admin/snippets/components/stats_cards.html +0 -0
  234. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/templates/admin/snippets/components/stats_tiles.html +0 -0
  235. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/templates/admin/snippets/components/system_health.html +0 -0
  236. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/templates/admin/snippets/components/system_metrics.html +0 -0
  237. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/templates/admin/snippets/components/user_permissions.html +0 -0
  238. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/templates/admin/snippets/tabs/app_stats_tab.html +0 -0
  239. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/templates/admin/snippets/tabs/commands_tab.html +0 -0
  240. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/templates/admin/snippets/tabs/overview_tab.html +0 -0
  241. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/templates/admin/snippets/tabs/stats_tab.html +0 -0
  242. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/templates/admin/snippets/tabs/users_tab.html +0 -0
  243. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/templates/admin/snippets/zones/zones_table.html +0 -0
  244. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/templates/emails/base_email.html +0 -0
  245. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/utils/__init__.py +0 -0
  246. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/utils/path_resolution.py +0 -0
  247. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/utils/smart_defaults.py +0 -0
  248. {django_cfg-1.1.49 → django_cfg-1.1.51}/src/django_cfg/version_check.py +0 -0
@@ -76,3 +76,5 @@ package-lock.json
76
76
  *.bak
77
77
  *.tmp
78
78
  *.temp
79
+
80
+ /cache
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2025 Unrealos Team
3
+ Copyright (c) 2025 ReformsAI Team
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -1,14 +1,14 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-cfg
3
- Version: 1.1.49
3
+ Version: 1.1.51
4
4
  Summary: 🚀 Production-ready Django configuration framework with type-safe settings, smart automation, and modern developer experience
5
5
  Project-URL: Homepage, https://github.com/markolofsen/django-cfg
6
6
  Project-URL: Documentation, https://django-cfg.readthedocs.io
7
7
  Project-URL: Repository, https://github.com/markolofsen/django-cfg.git
8
8
  Project-URL: Issues, https://github.com/markolofsen/django-cfg/issues
9
9
  Project-URL: Changelog, https://github.com/markolofsen/django-cfg/blob/main/CHANGELOG.md
10
- Author-email: UnrealOS Team <dev@unrealos.com>
11
- Maintainer-email: UnrealOS Team <dev@unrealos.com>
10
+ Author-email: ReformsAI Team <dev@reforms.ai>
11
+ Maintainer-email: ReformsAI Team <dev@reforms.ai>
12
12
  License: MIT
13
13
  License-File: LICENSE
14
14
  Keywords: configuration,developer-experience,django,pydantic,settings,type-safety
@@ -1090,6 +1090,6 @@ MIT License - see [LICENSE](LICENSE) file for details.
1090
1090
 
1091
1091
  ---
1092
1092
 
1093
- **Made with ❤️ by the UnrealOS Team**
1093
+ **Made with ❤️ by the ReformsAI Team**
1094
1094
 
1095
1095
  *Django-CFG: Because configuration should be simple, safe, and powerful.*
@@ -969,6 +969,6 @@ MIT License - see [LICENSE](LICENSE) file for details.
969
969
 
970
970
  ---
971
971
 
972
- **Made with ❤️ by the UnrealOS Team**
972
+ **Made with ❤️ by the ReformsAI Team**
973
973
 
974
974
  *Django-CFG: Because configuration should be simple, safe, and powerful.*
@@ -4,15 +4,15 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "django-cfg"
7
- version = "1.1.49"
7
+ version = "1.1.51"
8
8
  description = "🚀 Production-ready Django configuration framework with type-safe settings, smart automation, and modern developer experience"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
11
11
  authors = [
12
- {name = "UnrealOS Team", email = "dev@unrealos.com"},
12
+ {name = "ReformsAI Team", email = "dev@reforms.ai"},
13
13
  ]
14
14
  maintainers = [
15
- {name = "UnrealOS Team", email = "dev@unrealos.com"},
15
+ {name = "ReformsAI Team", email = "dev@reforms.ai"},
16
16
  ]
17
17
  keywords = [
18
18
  "django",
@@ -38,9 +38,9 @@ default_app_config = "django_cfg.apps.DjangoCfgConfig"
38
38
  from typing import TYPE_CHECKING
39
39
 
40
40
  # Version information
41
- __version__ = "1.1.49"
42
- __author__ = "Unrealos Team"
43
- __email__ = "info@unrealos.com"
41
+ __version__ = "1.1.51"
42
+ __author__ = "ReformsAI Team"
43
+ __email__ = "info@reforms.ai"
44
44
  __license__ = "MIT"
45
45
 
46
46
  # Core exports - only import when needed to avoid circular imports
@@ -7,6 +7,7 @@ from .otp import OTPSecretAdmin
7
7
  from .registration_source import RegistrationSourceAdmin, UserRegistrationSourceAdmin
8
8
  from .activity import UserActivityAdmin
9
9
  from .group import GroupAdmin
10
+ from .twilio_response import TwilioResponseAdmin
10
11
 
11
12
  __all__ = [
12
13
  'CustomUserAdmin',
@@ -15,4 +16,5 @@ __all__ = [
15
16
  'UserRegistrationSourceAdmin',
16
17
  'UserActivityAdmin',
17
18
  'GroupAdmin',
19
+ 'TwilioResponseAdmin',
18
20
  ]
@@ -96,3 +96,57 @@ class ActivityTypeFilter(admin.SimpleListFilter):
96
96
  elif self.value():
97
97
  return queryset.filter(activity_type=self.value())
98
98
  return queryset
99
+
100
+
101
+ class TwilioResponseStatusFilter(admin.SimpleListFilter):
102
+ title = "Response Status"
103
+ parameter_name = "twilio_status"
104
+
105
+ def lookups(self, request, model_admin):
106
+ return (
107
+ ("successful", "Successful"),
108
+ ("failed", "Failed"),
109
+ ("pending", "Pending"),
110
+ ("with_errors", "With Errors"),
111
+ ("recent", "Recent (24h)"),
112
+ )
113
+
114
+ def queryset(self, request, queryset):
115
+ now = timezone.now()
116
+ if self.value() == "successful":
117
+ return queryset.filter(
118
+ status__in=['sent', 'delivered', 'approved'],
119
+ error_code__isnull=True
120
+ )
121
+ elif self.value() == "failed":
122
+ return queryset.filter(
123
+ status__in=['failed', 'undelivered', 'rejected']
124
+ )
125
+ elif self.value() == "pending":
126
+ return queryset.filter(status='pending')
127
+ elif self.value() == "with_errors":
128
+ return queryset.exclude(error_code__isnull=True)
129
+ elif self.value() == "recent":
130
+ return queryset.filter(created_at__gte=now - timedelta(hours=24))
131
+ return queryset
132
+
133
+
134
+ class TwilioResponseTypeFilter(admin.SimpleListFilter):
135
+ title = "Response Type"
136
+ parameter_name = "twilio_response_type"
137
+
138
+ def lookups(self, request, model_admin):
139
+ return (
140
+ ("api_send", "API Send"),
141
+ ("api_verify", "API Verify"),
142
+ ("webhook_status", "Webhook Status"),
143
+ ("webhook_delivery", "Webhook Delivery"),
144
+ ("otp_related", "OTP Related"),
145
+ )
146
+
147
+ def queryset(self, request, queryset):
148
+ if self.value() == "otp_related":
149
+ return queryset.filter(otp_secret__isnull=False)
150
+ elif self.value():
151
+ return queryset.filter(response_type=self.value())
152
+ return queryset
@@ -8,16 +8,18 @@ from unfold.admin import ModelAdmin
8
8
 
9
9
  from ..models import OTPSecret
10
10
  from .filters import OTPStatusFilter
11
+ from .twilio_response import TwilioResponseInline
11
12
 
12
13
 
13
14
  @admin.register(OTPSecret)
14
15
  class OTPSecretAdmin(ModelAdmin):
15
- list_display = ["recipient", "channel_type", "secret", "status", "created", "expires"]
16
+ list_display = ["recipient", "channel_type", "secret", "status", "twilio_responses_count", "created", "expires"]
16
17
  list_display_links = ["recipient", "secret"]
17
18
  list_filter = [OTPStatusFilter, "channel_type", "is_used", "created_at"]
18
19
  search_fields = ["recipient", "secret"]
19
20
  readonly_fields = ["created_at", "expires_at"]
20
21
  ordering = ["-created_at"]
22
+ inlines = [TwilioResponseInline]
21
23
 
22
24
  fieldsets = (
23
25
  (
@@ -57,3 +59,12 @@ class OTPSecretAdmin(ModelAdmin):
57
59
  return naturaltime(obj.expires_at)
58
60
 
59
61
  expires.short_description = "Expires"
62
+
63
+ def twilio_responses_count(self, obj):
64
+ """Count of related Twilio responses."""
65
+ count = obj.twilio_responses.count()
66
+ if count == 0:
67
+ return "—"
68
+ return f"{count} response{'s' if count != 1 else ''}"
69
+
70
+ twilio_responses_count.short_description = "Twilio"
@@ -0,0 +1,222 @@
1
+ """
2
+ Twilio Response admin configuration.
3
+ """
4
+
5
+ from django.contrib import admin
6
+ from django.contrib.humanize.templatetags.humanize import naturaltime
7
+ from django.utils.html import format_html
8
+ from unfold.admin import ModelAdmin
9
+
10
+ from ..models import TwilioResponse
11
+ from .filters import TwilioResponseStatusFilter, TwilioResponseTypeFilter
12
+
13
+
14
+ class TwilioResponseInline(admin.TabularInline):
15
+ """Inline for showing Twilio responses in related models."""
16
+ model = TwilioResponse
17
+ extra = 0
18
+ readonly_fields = ['created_at', 'status', 'message_sid', 'error_code']
19
+ fields = ['response_type', 'service_type', 'status', 'message_sid', 'error_code', 'created_at']
20
+
21
+ def has_add_permission(self, request, obj=None):
22
+ return False
23
+
24
+
25
+ @admin.register(TwilioResponse)
26
+ class TwilioResponseAdmin(ModelAdmin):
27
+ list_display = [
28
+ 'identifier',
29
+ 'service_type',
30
+ 'response_type',
31
+ 'status_display',
32
+ 'recipient',
33
+ 'price_display',
34
+ 'created_display',
35
+ 'has_error_display'
36
+ ]
37
+ list_display_links = ['identifier']
38
+ list_filter = [
39
+ TwilioResponseStatusFilter,
40
+ TwilioResponseTypeFilter,
41
+ 'service_type',
42
+ 'response_type',
43
+ 'created_at',
44
+ ]
45
+ search_fields = [
46
+ 'message_sid',
47
+ 'verification_sid',
48
+ 'to_number',
49
+ 'error_message',
50
+ 'otp_secret__recipient'
51
+ ]
52
+ readonly_fields = [
53
+ 'created_at',
54
+ 'updated_at',
55
+ 'twilio_created_at',
56
+ 'response_data_display',
57
+ 'request_data_display'
58
+ ]
59
+ ordering = ['-created_at']
60
+
61
+ fieldsets = (
62
+ (
63
+ 'Basic Information',
64
+ {
65
+ 'fields': (
66
+ 'response_type',
67
+ 'service_type',
68
+ 'status',
69
+ 'otp_secret'
70
+ ),
71
+ },
72
+ ),
73
+ (
74
+ 'Twilio Identifiers',
75
+ {
76
+ 'fields': (
77
+ 'message_sid',
78
+ 'verification_sid',
79
+ ),
80
+ },
81
+ ),
82
+ (
83
+ 'Recipients',
84
+ {
85
+ 'fields': (
86
+ 'to_number',
87
+ 'from_number',
88
+ ),
89
+ },
90
+ ),
91
+ (
92
+ 'Error Information',
93
+ {
94
+ 'fields': (
95
+ 'error_code',
96
+ 'error_message',
97
+ ),
98
+ 'classes': ('collapse',),
99
+ },
100
+ ),
101
+ (
102
+ 'Pricing',
103
+ {
104
+ 'fields': (
105
+ 'price',
106
+ 'price_unit',
107
+ ),
108
+ 'classes': ('collapse',),
109
+ },
110
+ ),
111
+ (
112
+ 'Request/Response Data',
113
+ {
114
+ 'fields': (
115
+ 'request_data_display',
116
+ 'response_data_display',
117
+ ),
118
+ 'classes': ('collapse',),
119
+ },
120
+ ),
121
+ (
122
+ 'Timestamps',
123
+ {
124
+ 'fields': (
125
+ 'created_at',
126
+ 'updated_at',
127
+ 'twilio_created_at',
128
+ ),
129
+ 'classes': ('collapse',),
130
+ },
131
+ ),
132
+ )
133
+
134
+ def identifier(self, obj):
135
+ """Get the main identifier for the response."""
136
+ return obj.message_sid or obj.verification_sid or '—'
137
+ identifier.short_description = 'Identifier'
138
+
139
+ def status_display(self, obj):
140
+ """Display status with color coding."""
141
+ if obj.has_error:
142
+ return format_html(
143
+ '<span style="color: #dc3545;">❌ {}</span>',
144
+ obj.status or 'Error'
145
+ )
146
+ elif obj.is_successful:
147
+ return format_html(
148
+ '<span style="color: #28a745;">✅ {}</span>',
149
+ obj.status or 'Success'
150
+ )
151
+ else:
152
+ return format_html(
153
+ '<span style="color: #ffc107;">⏳ {}</span>',
154
+ obj.status or 'Unknown'
155
+ )
156
+ status_display.short_description = 'Status'
157
+
158
+ def recipient(self, obj):
159
+ """Display recipient with masking for privacy."""
160
+ if not obj.to_number:
161
+ return '—'
162
+
163
+ # Mask phone numbers and emails for privacy
164
+ recipient = obj.to_number
165
+ if '@' in recipient:
166
+ # Email masking
167
+ local, domain = recipient.split('@', 1)
168
+ masked_local = local[:2] + '*' * (len(local) - 2)
169
+ return f"{masked_local}@{domain}"
170
+ else:
171
+ # Phone masking
172
+ return f"***{recipient[-4:]}" if len(recipient) > 4 else "***"
173
+ recipient.short_description = 'Recipient'
174
+
175
+ def price_display(self, obj):
176
+ """Display price with currency."""
177
+ if obj.price and obj.price_unit:
178
+ return f"{obj.price} {obj.price_unit.upper()}"
179
+ return '—'
180
+ price_display.short_description = 'Price'
181
+
182
+ def created_display(self, obj):
183
+ """Display created time with natural time."""
184
+ return naturaltime(obj.created_at)
185
+ created_display.short_description = 'Created'
186
+
187
+ def has_error_display(self, obj):
188
+ """Display error status."""
189
+ if obj.has_error:
190
+ return format_html('<span style="color: #dc3545;">❌</span>')
191
+ return format_html('<span style="color: #28a745;">✅</span>')
192
+ has_error_display.short_description = 'Error'
193
+
194
+ def request_data_display(self, obj):
195
+ """Display formatted request data."""
196
+ if not obj.request_data:
197
+ return '—'
198
+
199
+ import json
200
+ try:
201
+ formatted = json.dumps(obj.request_data, indent=2, ensure_ascii=False)
202
+ return format_html('<pre style="font-size: 12px;">{}</pre>', formatted)
203
+ except (TypeError, ValueError):
204
+ return str(obj.request_data)
205
+ request_data_display.short_description = 'Request Data'
206
+
207
+ def response_data_display(self, obj):
208
+ """Display formatted response data."""
209
+ if not obj.response_data:
210
+ return '—'
211
+
212
+ import json
213
+ try:
214
+ formatted = json.dumps(obj.response_data, indent=2, ensure_ascii=False)
215
+ return format_html('<pre style="font-size: 12px;">{}</pre>', formatted)
216
+ except (TypeError, ValueError):
217
+ return str(obj.response_data)
218
+ response_data_display.short_description = 'Response Data'
219
+
220
+ def get_queryset(self, request):
221
+ """Optimize queryset with select_related."""
222
+ return super().get_queryset(request).select_related('otp_secret')
@@ -246,6 +246,11 @@ class UserManager(UserManager):
246
246
  return user.first_name
247
247
  elif user.last_name:
248
248
  return user.last_name
249
+
250
+ # For phone users with temp email, use display_username instead
251
+ if user.email and user.email.startswith('phone_') and '@temp.' in user.email:
252
+ return self.get_display_username(user)
253
+
249
254
  return user.email
250
255
 
251
256
  def get_initials(self, user) -> str:
@@ -262,6 +267,17 @@ class UserManager(UserManager):
262
267
  return user.first_name[0].upper()
263
268
  elif user.last_name:
264
269
  return user.last_name[0].upper()
270
+
271
+ # For phone users with temp email, use username initials instead
272
+ if user.email and user.email.startswith('phone_') and '@temp.' in user.email:
273
+ if user.username:
274
+ # Take first two characters of username
275
+ clean_username = user.username.replace("_", "").replace("-", "").replace(".", "")
276
+ if len(clean_username) >= 2:
277
+ return f"{clean_username[0]}{clean_username[1]}".upper()
278
+ elif len(clean_username) == 1:
279
+ return clean_username[0].upper()
280
+
265
281
  return user.email[0].upper()
266
282
 
267
283
  def get_display_username(self, user) -> str:
@@ -0,0 +1,43 @@
1
+ # Generated by Django 5.2.6 on 2025-09-16 09:23
2
+
3
+ import django.db.models.deletion
4
+ from django.db import migrations, models
5
+
6
+
7
+ class Migration(migrations.Migration):
8
+
9
+ dependencies = [
10
+ ('django_cfg_accounts', '0002_add_phone_otp_clean'),
11
+ ]
12
+
13
+ operations = [
14
+ migrations.CreateModel(
15
+ name='TwilioResponse',
16
+ fields=[
17
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
18
+ ('response_type', models.CharField(choices=[('api_send', 'API Send Request'), ('api_verify', 'API Verify Request'), ('webhook_status', 'Webhook Status Update'), ('webhook_delivery', 'Webhook Delivery Report')], max_length=20)),
19
+ ('service_type', models.CharField(choices=[('whatsapp', 'WhatsApp'), ('sms', 'SMS'), ('voice', 'Voice'), ('email', 'Email'), ('verify', 'Verify API')], max_length=10)),
20
+ ('message_sid', models.CharField(blank=True, help_text='Twilio Message SID', max_length=34)),
21
+ ('verification_sid', models.CharField(blank=True, help_text='Twilio Verification SID', max_length=34)),
22
+ ('request_data', models.JSONField(default=dict, help_text='Original request parameters')),
23
+ ('response_data', models.JSONField(default=dict, help_text='Twilio API response')),
24
+ ('status', models.CharField(blank=True, help_text='Message/Verification status', max_length=20)),
25
+ ('error_code', models.CharField(blank=True, help_text='Twilio error code', max_length=10)),
26
+ ('error_message', models.TextField(blank=True, help_text='Error description')),
27
+ ('to_number', models.CharField(blank=True, help_text='Recipient phone/email', max_length=20)),
28
+ ('from_number', models.CharField(blank=True, help_text='Sender phone/email', max_length=20)),
29
+ ('price', models.DecimalField(blank=True, decimal_places=6, max_digits=10, null=True)),
30
+ ('price_unit', models.CharField(blank=True, help_text='Currency code', max_length=3)),
31
+ ('created_at', models.DateTimeField(auto_now_add=True)),
32
+ ('updated_at', models.DateTimeField(auto_now=True)),
33
+ ('twilio_created_at', models.DateTimeField(blank=True, help_text='Timestamp from Twilio', null=True)),
34
+ ('otp_secret', models.ForeignKey(blank=True, help_text='Related OTP if applicable', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='twilio_responses', to='django_cfg_accounts.otpsecret')),
35
+ ],
36
+ options={
37
+ 'verbose_name': 'Twilio Response',
38
+ 'verbose_name_plural': 'Twilio Responses',
39
+ 'ordering': ['-created_at'],
40
+ 'indexes': [models.Index(fields=['message_sid'], name='django_cfg__message_c37dcd_idx'), models.Index(fields=['verification_sid'], name='django_cfg__verific_7de689_idx'), models.Index(fields=['status', 'created_at'], name='django_cfg__status_95d8c8_idx'), models.Index(fields=['response_type', 'service_type'], name='django_cfg__respons_20ca26_idx')],
41
+ },
42
+ ),
43
+ ]
@@ -203,6 +203,99 @@ class OTPSecret(models.Model):
203
203
  ]
204
204
 
205
205
 
206
+ class TwilioResponse(models.Model):
207
+ """
208
+ Store Twilio API responses and webhook events.
209
+
210
+ This model tracks all interactions with Twilio including:
211
+ - API responses from sending messages/OTP
212
+ - Webhook events for delivery status updates
213
+ - Error tracking and debugging information
214
+ """
215
+
216
+ RESPONSE_TYPES = [
217
+ ('api_send', 'API Send Request'),
218
+ ('api_verify', 'API Verify Request'),
219
+ ('webhook_status', 'Webhook Status Update'),
220
+ ('webhook_delivery', 'Webhook Delivery Report'),
221
+ ]
222
+
223
+ SERVICE_TYPES = [
224
+ ('whatsapp', 'WhatsApp'),
225
+ ('sms', 'SMS'),
226
+ ('voice', 'Voice'),
227
+ ('email', 'Email'),
228
+ ('verify', 'Verify API'),
229
+ ]
230
+
231
+ # Basic info
232
+ response_type = models.CharField(max_length=20, choices=RESPONSE_TYPES)
233
+ service_type = models.CharField(max_length=10, choices=SERVICE_TYPES)
234
+
235
+ # Twilio identifiers
236
+ message_sid = models.CharField(max_length=34, blank=True, help_text="Twilio Message SID")
237
+ verification_sid = models.CharField(max_length=34, blank=True, help_text="Twilio Verification SID")
238
+
239
+ # Request/Response data
240
+ request_data = models.JSONField(default=dict, help_text="Original request parameters")
241
+ response_data = models.JSONField(default=dict, help_text="Twilio API response")
242
+
243
+ # Status tracking
244
+ status = models.CharField(max_length=20, blank=True, help_text="Message/Verification status")
245
+ error_code = models.CharField(max_length=10, blank=True, help_text="Twilio error code")
246
+ error_message = models.TextField(blank=True, help_text="Error description")
247
+
248
+ # Recipient info
249
+ to_number = models.CharField(max_length=20, blank=True, help_text="Recipient phone/email")
250
+ from_number = models.CharField(max_length=20, blank=True, help_text="Sender phone/email")
251
+
252
+ # Pricing
253
+ price = models.DecimalField(max_digits=10, decimal_places=6, null=True, blank=True)
254
+ price_unit = models.CharField(max_length=3, blank=True, help_text="Currency code")
255
+
256
+ # Timestamps
257
+ created_at = models.DateTimeField(auto_now_add=True)
258
+ updated_at = models.DateTimeField(auto_now=True)
259
+ twilio_created_at = models.DateTimeField(null=True, blank=True, help_text="Timestamp from Twilio")
260
+
261
+ # Relations
262
+ otp_secret = models.ForeignKey(
263
+ 'OTPSecret',
264
+ on_delete=models.SET_NULL,
265
+ null=True,
266
+ blank=True,
267
+ related_name='twilio_responses',
268
+ help_text="Related OTP if applicable"
269
+ )
270
+
271
+ class Meta:
272
+ app_label = 'django_cfg_accounts'
273
+ verbose_name = 'Twilio Response'
274
+ verbose_name_plural = 'Twilio Responses'
275
+ ordering = ['-created_at']
276
+ indexes = [
277
+ models.Index(fields=['message_sid']),
278
+ models.Index(fields=['verification_sid']),
279
+ models.Index(fields=['status', 'created_at']),
280
+ models.Index(fields=['response_type', 'service_type']),
281
+ ]
282
+
283
+ def __str__(self):
284
+ identifier = self.message_sid or self.verification_sid or 'Unknown'
285
+ return f"{self.get_service_type_display()} {self.get_response_type_display()} - {identifier}"
286
+
287
+ @property
288
+ def is_successful(self):
289
+ """Check if the response indicates success."""
290
+ success_statuses = ['sent', 'delivered', 'pending', 'approved']
291
+ return self.status.lower() in success_statuses if self.status else False
292
+
293
+ @property
294
+ def has_error(self):
295
+ """Check if the response has an error."""
296
+ return bool(self.error_code or self.error_message)
297
+
298
+
206
299
  class UserActivity(models.Model):
207
300
  """
208
301
  User activity log.
@@ -26,7 +26,7 @@ class OTPRequestSerializer(serializers.Serializer):
26
26
  source_url = serializers.URLField(
27
27
  required=False,
28
28
  allow_blank=True,
29
- help_text="Source URL for tracking registration (e.g., https://unrealos.com)",
29
+ help_text="Source URL for tracking registration (e.g., https://reforms.ai)",
30
30
  )
31
31
 
32
32
  def validate_identifier(self, value):
@@ -76,7 +76,7 @@ class OTPVerifySerializer(serializers.Serializer):
76
76
  source_url = serializers.URLField(
77
77
  required=False,
78
78
  allow_blank=True,
79
- help_text="Source URL for tracking login (e.g., https://unrealos.com)",
79
+ help_text="Source URL for tracking login (e.g., https://reforms.ai)",
80
80
  )
81
81
 
82
82
  def validate_identifier(self, value):