django-cfg 1.1.74__tar.gz → 1.1.75__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 (276) hide show
  1. {django_cfg-1.1.74 → django_cfg-1.1.75}/PKG-INFO +1 -1
  2. {django_cfg-1.1.74 → django_cfg-1.1.75}/pyproject.toml +1 -1
  3. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/__init__.py +1 -1
  4. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/core/config.py +1 -0
  5. django_cfg-1.1.75/src/django_cfg/middleware/README.md +318 -0
  6. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/middleware/__init__.py +2 -0
  7. django_cfg-1.1.75/src/django_cfg/middleware/public_endpoints.py +182 -0
  8. django_cfg-1.1.74/src/django_cfg/middleware/README.md +0 -160
  9. {django_cfg-1.1.74 → django_cfg-1.1.75}/.gitignore +0 -0
  10. {django_cfg-1.1.74 → django_cfg-1.1.75}/LICENSE +0 -0
  11. {django_cfg-1.1.74 → django_cfg-1.1.75}/MANIFEST.in +0 -0
  12. {django_cfg-1.1.74 → django_cfg-1.1.75}/README.md +0 -0
  13. {django_cfg-1.1.74 → django_cfg-1.1.75}/requirements-dev.txt +0 -0
  14. {django_cfg-1.1.74 → django_cfg-1.1.75}/requirements-test.txt +0 -0
  15. {django_cfg-1.1.74 → django_cfg-1.1.75}/requirements.txt +0 -0
  16. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/README.md +0 -0
  17. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/__init__.py +0 -0
  18. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/accounts/README.md +0 -0
  19. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/accounts/__init__.py +0 -0
  20. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/accounts/admin/__init__.py +0 -0
  21. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/accounts/admin/activity.py +0 -0
  22. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/accounts/admin/filters.py +0 -0
  23. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/accounts/admin/group.py +0 -0
  24. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/accounts/admin/inlines.py +0 -0
  25. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/accounts/admin/otp.py +0 -0
  26. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/accounts/admin/registration_source.py +0 -0
  27. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/accounts/admin/resources.py +0 -0
  28. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/accounts/admin/twilio_response.py +0 -0
  29. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/accounts/admin/user.py +0 -0
  30. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/accounts/apps.py +0 -0
  31. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/accounts/management/commands/test_otp.py +0 -0
  32. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/accounts/managers/__init__.py +0 -0
  33. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/accounts/managers/user_manager.py +0 -0
  34. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/accounts/migrations/0001_initial.py +0 -0
  35. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/accounts/migrations/0002_add_phone_otp_clean.py +0 -0
  36. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/accounts/migrations/0003_twilioresponse.py +0 -0
  37. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/accounts/migrations/0004_delete_twilioresponse.py +0 -0
  38. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/accounts/migrations/0005_twilioresponse.py +0 -0
  39. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/accounts/migrations/__init__.py +0 -0
  40. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/accounts/models.py +0 -0
  41. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/accounts/serializers/__init__.py +0 -0
  42. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/accounts/serializers/otp.py +0 -0
  43. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/accounts/serializers/profile.py +0 -0
  44. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/accounts/serializers/webhook.py +0 -0
  45. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/accounts/services/__init__.py +0 -0
  46. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/accounts/services/activity_service.py +0 -0
  47. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/accounts/services/otp_service.py +0 -0
  48. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/accounts/signals.py +0 -0
  49. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/accounts/templates/emails/base_email.html +0 -0
  50. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/accounts/templates/emails/base_email.txt +0 -0
  51. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/accounts/templates/emails/otp_email.html +0 -0
  52. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/accounts/templates/emails/otp_email.txt +0 -0
  53. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/accounts/templates/emails/welcome_email.html +0 -0
  54. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/accounts/templates/emails/welcome_email.txt +0 -0
  55. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/accounts/urls.py +0 -0
  56. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/accounts/utils/notifications.py +0 -0
  57. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/accounts/views/__init__.py +0 -0
  58. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/accounts/views/otp.py +0 -0
  59. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/accounts/views/profile.py +0 -0
  60. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/accounts/views/webhook.py +0 -0
  61. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/api/__init__.py +0 -0
  62. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/api/commands/__init__.py +0 -0
  63. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/api/commands/urls.py +0 -0
  64. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/api/commands/views.py +0 -0
  65. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/api/health/__init__.py +0 -0
  66. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/api/health/urls.py +0 -0
  67. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/api/health/views.py +0 -0
  68. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/leads/README.md +0 -0
  69. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/leads/__init__.py +0 -0
  70. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/leads/admin/__init__.py +0 -0
  71. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/leads/admin/leads_admin.py +0 -0
  72. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/leads/admin/resources.py +0 -0
  73. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/leads/apps.py +0 -0
  74. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/leads/migrations/0001_initial.py +0 -0
  75. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/leads/migrations/__init__.py +0 -0
  76. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/leads/models.py +0 -0
  77. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/leads/serializers.py +0 -0
  78. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/leads/signals.py +0 -0
  79. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/leads/tests.py +0 -0
  80. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/leads/urls.py +0 -0
  81. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/leads/views.py +0 -0
  82. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/newsletter/README.md +0 -0
  83. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/newsletter/__init__.py +0 -0
  84. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/newsletter/admin/__init__.py +0 -0
  85. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/newsletter/admin/filters.py +0 -0
  86. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/newsletter/admin/newsletter_admin.py +0 -0
  87. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/newsletter/admin/resources.py +0 -0
  88. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/newsletter/apps.py +0 -0
  89. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/newsletter/management/__init__.py +0 -0
  90. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/newsletter/management/commands/__init__.py +0 -0
  91. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/newsletter/management/commands/test_newsletter.py +0 -0
  92. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/newsletter/managers/README.md +0 -0
  93. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/newsletter/managers/__init__.py +0 -0
  94. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/newsletter/migrations/0001_initial.py +0 -0
  95. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/newsletter/migrations/__init__.py +0 -0
  96. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/newsletter/models.py +0 -0
  97. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/newsletter/serializers.py +0 -0
  98. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/newsletter/services/email_service.py +0 -0
  99. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/newsletter/signals.py +0 -0
  100. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/newsletter/urls.py +0 -0
  101. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/newsletter/utils/__init__.py +0 -0
  102. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/newsletter/views/__init__.py +0 -0
  103. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/newsletter/views/campaigns.py +0 -0
  104. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/newsletter/views/emails.py +0 -0
  105. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/newsletter/views/newsletters.py +0 -0
  106. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/newsletter/views/subscriptions.py +0 -0
  107. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/newsletter/views/tracking.py +0 -0
  108. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/support/__init__.py +0 -0
  109. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/support/admin/__init__.py +0 -0
  110. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/support/admin/filters.py +0 -0
  111. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/support/admin/resources.py +0 -0
  112. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/support/admin/support_admin.py +0 -0
  113. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/support/apps.py +0 -0
  114. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/support/managers/message_manager.py +0 -0
  115. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/support/managers/ticket_manager.py +0 -0
  116. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/support/migrations/0001_initial.py +0 -0
  117. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/support/migrations/0002_alter_message_ticket.py +0 -0
  118. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/support/migrations/__init__.py +0 -0
  119. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/support/models.py +0 -0
  120. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/support/serializers.py +0 -0
  121. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/support/signals.py +0 -0
  122. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/support/templates/support/chat/access_denied.html +0 -0
  123. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/support/templates/support/chat/ticket_chat.html +0 -0
  124. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/support/urls.py +0 -0
  125. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/support/utils/__init__.py +0 -0
  126. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/support/utils/support_email_service.py +0 -0
  127. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/support/views/__init__.py +0 -0
  128. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/support/views/admin.py +0 -0
  129. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/support/views/api.py +0 -0
  130. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/support/views/chat.py +0 -0
  131. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/tasks/__init__.py +0 -0
  132. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/tasks/admin.py +0 -0
  133. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/tasks/apps.py +0 -0
  134. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/tasks/serializers.py +0 -0
  135. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/tasks/static/tasks/css/dashboard.css +0 -0
  136. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/tasks/static/tasks/js/api.js +0 -0
  137. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/tasks/static/tasks/js/dashboard.js +0 -0
  138. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/tasks/static/tasks/js/modals.js +0 -0
  139. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/tasks/static/tasks/js/notifications.js +0 -0
  140. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/tasks/static/tasks/js/task-monitor.js +0 -0
  141. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/tasks/static/tasks/js/theme.js +0 -0
  142. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/tasks/templates/tasks/base.html +0 -0
  143. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/tasks/templates/tasks/components/info_cards.html +0 -0
  144. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/tasks/templates/tasks/components/management_actions.html +0 -0
  145. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/tasks/templates/tasks/components/overview_tab.html +0 -0
  146. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/tasks/templates/tasks/components/queues_tab.html +0 -0
  147. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/tasks/templates/tasks/components/status_cards.html +0 -0
  148. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/tasks/templates/tasks/components/tab_navigation.html +0 -0
  149. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/tasks/templates/tasks/components/task_details_modal.html +0 -0
  150. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/tasks/templates/tasks/components/tasks_tab.html +0 -0
  151. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/tasks/templates/tasks/components/workers_tab.html +0 -0
  152. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/tasks/templates/tasks/dashboard.html +0 -0
  153. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/tasks/urls.py +0 -0
  154. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/tasks/views.py +0 -0
  155. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps/urls.py +0 -0
  156. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/apps.py +0 -0
  157. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/archive/django_sample.zip +0 -0
  158. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/cli/README.md +0 -0
  159. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/cli/__init__.py +0 -0
  160. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/cli/commands/__init__.py +0 -0
  161. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/cli/commands/create_project.py +0 -0
  162. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/cli/commands/info.py +0 -0
  163. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/cli/main.py +0 -0
  164. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/cli/utils.py +0 -0
  165. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/core/__init__.py +0 -0
  166. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/core/environment.py +0 -0
  167. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/core/generation.py +0 -0
  168. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/core/validation.py +0 -0
  169. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/exceptions.py +0 -0
  170. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/integration.py +0 -0
  171. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/management/__init__.py +0 -0
  172. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/management/commands/__init__.py +0 -0
  173. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/management/commands/check_settings.py +0 -0
  174. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/management/commands/clear_constance.py +0 -0
  175. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/management/commands/create_token.py +0 -0
  176. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/management/commands/generate.py +0 -0
  177. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/management/commands/list_urls.py +0 -0
  178. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/management/commands/migrate_all.py +0 -0
  179. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/management/commands/migrator.py +0 -0
  180. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/management/commands/rundramatiq.py +0 -0
  181. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/management/commands/runserver_ngrok.py +0 -0
  182. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/management/commands/script.py +0 -0
  183. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/management/commands/show_config.py +0 -0
  184. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/management/commands/show_urls.py +0 -0
  185. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/management/commands/superuser.py +0 -0
  186. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/management/commands/task_clear.py +0 -0
  187. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/management/commands/task_status.py +0 -0
  188. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/management/commands/test_email.py +0 -0
  189. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/management/commands/test_telegram.py +0 -0
  190. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/management/commands/test_twilio.py +0 -0
  191. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/management/commands/tree.py +0 -0
  192. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/management/commands/validate_config.py +0 -0
  193. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/middleware/user_activity.py +0 -0
  194. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/models/__init__.py +0 -0
  195. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/models/cache.py +0 -0
  196. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/models/constance.py +0 -0
  197. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/models/database.py +0 -0
  198. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/models/drf.py +0 -0
  199. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/models/jwt.py +0 -0
  200. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/models/limits.py +0 -0
  201. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/models/ngrok.py +0 -0
  202. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/models/revolution.py +0 -0
  203. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/models/services.py +0 -0
  204. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/models/tasks.py +0 -0
  205. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/models/unfold.py +0 -0
  206. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/modules/__init__.py +0 -0
  207. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/modules/base.py +0 -0
  208. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/modules/django_currency/README.md +0 -0
  209. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/modules/django_currency/__init__.py +0 -0
  210. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/modules/django_currency/cache.py +0 -0
  211. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/modules/django_currency/converter.py +0 -0
  212. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/modules/django_currency/service.py +0 -0
  213. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/modules/django_email.py +0 -0
  214. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/modules/django_llm/README.md +0 -0
  215. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/modules/django_llm/__init__.py +0 -0
  216. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/modules/django_llm/example.py +0 -0
  217. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/modules/django_llm/llm/__init__.py +0 -0
  218. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/modules/django_llm/llm/cache.py +0 -0
  219. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/modules/django_llm/llm/client.py +0 -0
  220. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/modules/django_llm/llm/costs.py +0 -0
  221. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/modules/django_llm/llm/extractor.py +0 -0
  222. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/modules/django_llm/llm/models_cache.py +0 -0
  223. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/modules/django_llm/llm/tokenizer.py +0 -0
  224. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/modules/django_llm/translator/__init__.py +0 -0
  225. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/modules/django_llm/translator/cache.py +0 -0
  226. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/modules/django_llm/translator/translator.py +0 -0
  227. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/modules/django_logger.py +0 -0
  228. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/modules/django_ngrok.py +0 -0
  229. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/modules/django_tasks.py +0 -0
  230. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/modules/django_telegram.py +0 -0
  231. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/modules/django_twilio/README.md +0 -0
  232. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/modules/django_twilio/__init__.py +0 -0
  233. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/modules/django_twilio/exceptions.py +0 -0
  234. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/modules/django_twilio/models.py +0 -0
  235. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/modules/django_twilio/sendgrid_service.py +0 -0
  236. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/modules/django_twilio/service.py +0 -0
  237. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/modules/django_twilio/simple_service.py +0 -0
  238. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/modules/django_twilio/templates/guide.md +0 -0
  239. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/modules/django_twilio/templates/sendgrid_otp_email.html +0 -0
  240. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/modules/django_twilio/templates/sendgrid_test_data.json +0 -0
  241. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/modules/django_twilio/twilio_service.py +0 -0
  242. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/modules/dramatiq_setup.py +0 -0
  243. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/modules/logger.py +0 -0
  244. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/modules/unfold/__init__.py +0 -0
  245. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/modules/unfold/callbacks.py +0 -0
  246. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/modules/unfold/dashboard.py +0 -0
  247. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/modules/unfold/models.py +0 -0
  248. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/modules/unfold/system_monitor.py +0 -0
  249. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/modules/unfold/tailwind.py +0 -0
  250. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/pyproject.toml +0 -0
  251. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/routers.py +0 -0
  252. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/templates/__init__.py +0 -0
  253. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/templates/admin/index.html +0 -0
  254. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/templates/admin/layouts/dashboard_with_tabs.html +0 -0
  255. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/templates/admin/snippets/components/activity_tracker.html +0 -0
  256. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/templates/admin/snippets/components/charts_section.html +0 -0
  257. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/templates/admin/snippets/components/django_commands.html +0 -0
  258. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/templates/admin/snippets/components/quick_actions.html +0 -0
  259. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/templates/admin/snippets/components/recent_activity.html +0 -0
  260. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/templates/admin/snippets/components/recent_users_table.html +0 -0
  261. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/templates/admin/snippets/components/stats_cards.html +0 -0
  262. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/templates/admin/snippets/components/stats_tiles.html +0 -0
  263. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/templates/admin/snippets/components/system_health.html +0 -0
  264. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/templates/admin/snippets/components/system_metrics.html +0 -0
  265. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/templates/admin/snippets/components/user_permissions.html +0 -0
  266. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/templates/admin/snippets/tabs/app_stats_tab.html +0 -0
  267. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/templates/admin/snippets/tabs/commands_tab.html +0 -0
  268. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/templates/admin/snippets/tabs/overview_tab.html +0 -0
  269. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/templates/admin/snippets/tabs/stats_tab.html +0 -0
  270. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/templates/admin/snippets/tabs/users_tab.html +0 -0
  271. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/templates/admin/snippets/zones/zones_table.html +0 -0
  272. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/templates/emails/base_email.html +0 -0
  273. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/utils/__init__.py +0 -0
  274. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/utils/path_resolution.py +0 -0
  275. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/utils/smart_defaults.py +0 -0
  276. {django_cfg-1.1.74 → django_cfg-1.1.75}/src/django_cfg/version_check.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-cfg
3
- Version: 1.1.74
3
+ Version: 1.1.75
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
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "django-cfg"
7
- version = "1.1.74"
7
+ version = "1.1.75"
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"}
@@ -38,7 +38,7 @@ default_app_config = "django_cfg.apps.DjangoCfgConfig"
38
38
  from typing import TYPE_CHECKING
39
39
 
40
40
  # Version information
41
- __version__ = "1.1.74"
41
+ __version__ = "1.1.75"
42
42
  __author__ = "Unrealos Team"
43
43
  __email__ = "info@unrealos.com"
44
44
  __license__ = "MIT"
@@ -633,6 +633,7 @@ class DjangoConfig(BaseModel):
633
633
  "django.contrib.sessions.middleware.SessionMiddleware",
634
634
  "django.middleware.common.CommonMiddleware",
635
635
  "django.middleware.csrf.CsrfViewMiddleware",
636
+ "django_cfg.middleware.PublicEndpointsMiddleware", # Handle invalid JWT tokens on public endpoints
636
637
  "django.contrib.auth.middleware.AuthenticationMiddleware",
637
638
  "django.contrib.messages.middleware.MessageMiddleware",
638
639
  "django.middleware.clickjacking.XFrameOptionsMiddleware",
@@ -0,0 +1,318 @@
1
+ # 🛡️ Django CFG Middleware
2
+
3
+ Custom Django middleware components for Django CFG applications.
4
+
5
+ ## 📋 Contents
6
+
7
+ - [UserActivityMiddleware](#useractivitymiddleware) - User activity tracking
8
+ - [PublicEndpointsMiddleware](#publicendpointsmiddleware) - Ignore invalid JWT tokens on public endpoints
9
+
10
+ ## UserActivityMiddleware
11
+
12
+ Middleware for automatic user activity tracking by updating the `last_login` field on API requests.
13
+
14
+ ### ✨ Features
15
+
16
+ - ✅ Automatic `last_login` update on API requests
17
+ - ✅ Smart API request detection (JSON, DRF, REST methods)
18
+ - ✅ 5-minute update interval to prevent database spam
19
+ - ✅ In-memory caching for performance optimization
20
+ - ✅ Only works when `accounts` app is enabled
21
+ - ✅ KISS principle - no configuration needed
22
+
23
+ ### 🚀 Automatic Integration
24
+
25
+ The middleware is automatically included when `enable_accounts = True`:
26
+
27
+ ```python
28
+ class MyConfig(DjangoConfig):
29
+ enable_accounts = True # UserActivityMiddleware will be auto-included
30
+ ```
31
+
32
+ ### 🎯 API Request Detection
33
+
34
+ The middleware intelligently detects API requests using:
35
+
36
+ 1. **JSON Content-Type or Accept header**
37
+ ```
38
+ Content-Type: application/json
39
+ Accept: application/json
40
+ ```
41
+
42
+ 2. **DRF format parameter**
43
+ ```
44
+ ?format=json
45
+ ?format=api
46
+ ```
47
+
48
+ 3. **REST methods** (POST, PUT, PATCH, DELETE) on non-admin paths
49
+
50
+ 4. **Configured API prefixes**
51
+ - Django Revolution API: `/{api_prefix}/` (from config)
52
+ - Django CFG API: `/cfg/` (always)
53
+
54
+ ### 📊 Statistics
55
+
56
+ Get middleware statistics:
57
+
58
+ ```python
59
+ from django_cfg.middleware import UserActivityMiddleware
60
+
61
+ # In view or management command
62
+ middleware = UserActivityMiddleware()
63
+ stats = middleware.get_activity_stats()
64
+
65
+ print(stats)
66
+ # {
67
+ # 'tracked_users': 42,
68
+ # 'update_interval': 300,
69
+ # 'api_only': True,
70
+ # 'accounts_enabled': True,
71
+ # 'middleware_active': True
72
+ # }
73
+ ```
74
+
75
+ ### 🔍 Logging
76
+
77
+ The middleware logs activity at DEBUG level:
78
+
79
+ ```python
80
+ # settings.py
81
+ LOGGING = {
82
+ 'loggers': {
83
+ 'django_cfg.middleware.user_activity': {
84
+ 'level': 'DEBUG',
85
+ 'handlers': ['console'],
86
+ },
87
+ },
88
+ }
89
+ ```
90
+
91
+ ### 🎛️ Manual Integration
92
+
93
+ If you need to include the middleware manually:
94
+
95
+ ```python
96
+ # settings.py
97
+ MIDDLEWARE = [
98
+ # ... other middleware
99
+ 'django_cfg.middleware.UserActivityMiddleware',
100
+ ]
101
+ ```
102
+
103
+ ### 🔧 Performance
104
+
105
+ - **Caching**: Last update times are cached in memory
106
+ - **Batch updates**: Uses `update()` instead of `save()` for optimization
107
+ - **Auto-cleanup**: Cache automatically cleans up when exceeding 1000 users
108
+ - **Graceful errors**: Errors don't break request processing
109
+
110
+ ### 🎯 Admin Integration
111
+
112
+ The `last_login` field is automatically displayed in accounts admin:
113
+
114
+ - ✅ In user list view (`last_login_display`)
115
+ - ✅ In user detail view
116
+ - ✅ With human-readable time format
117
+
118
+ ### 🚨 Important Notes
119
+
120
+ 1. **Accounts only**: Middleware only works when `enable_accounts = True`
121
+ 2. **Authentication**: Only tracks authenticated users
122
+ 3. **Performance**: 5-minute interval prevents database spam
123
+ 4. **Safety**: Middleware doesn't break requests on errors
124
+
125
+ ### 📈 Monitoring
126
+
127
+ For user activity monitoring:
128
+
129
+ ```python
130
+ # In Django admin or management command
131
+ from django.contrib.auth import get_user_model
132
+ from django.utils import timezone
133
+ from datetime import timedelta
134
+
135
+ User = get_user_model()
136
+
137
+ # Active users in the last hour
138
+ active_users = User.objects.filter(
139
+ last_login__gte=timezone.now() - timedelta(hours=1)
140
+ ).count()
141
+
142
+ # Online users (last 5 minutes)
143
+ online_users = User.objects.filter(
144
+ last_login__gte=timezone.now() - timedelta(minutes=5)
145
+ ).count()
146
+ ```
147
+
148
+ ### 💡 Usage Examples
149
+
150
+ The middleware works automatically with no configuration needed:
151
+
152
+ ```python
153
+ # Your DjangoConfig
154
+ class MyProjectConfig(DjangoConfig):
155
+ enable_accounts = True # That's it! Middleware is active
156
+
157
+ # API requests will automatically update last_login:
158
+ # POST /cfg/accounts/profile/
159
+ # GET /api/users/?format=json
160
+ # PUT /cfg/newsletter/subscribe/
161
+ ```
162
+
163
+ ## PublicEndpointsMiddleware
164
+
165
+ Middleware that temporarily removes invalid JWT tokens from public endpoints to prevent authentication errors.
166
+
167
+ ### ✨ Features
168
+
169
+ - ✅ **Automatic activation** - No configuration needed, works out of the box
170
+ - ✅ **Smart endpoint detection** - Configurable regex patterns for public endpoints
171
+ - ✅ **JWT token detection** - Only processes requests with Bearer tokens
172
+ - ✅ **Temporary removal** - Auth headers are restored after request processing
173
+ - ✅ **Performance optimized** - Compiled regex patterns for fast matching
174
+ - ✅ **Detailed logging** - Debug information for troubleshooting
175
+ - ✅ **Statistics tracking** - Monitor middleware usage and effectiveness
176
+
177
+ ### 🎯 Problem Solved
178
+
179
+ When a frontend sends an invalid/expired JWT token to a public endpoint (like OTP request), Django's authentication middleware tries to authenticate the user and fails with "User not found" errors, even though the endpoint has `AllowAny` permissions.
180
+
181
+ This middleware temporarily removes the `Authorization` header for public endpoints, allowing them to work without authentication errors.
182
+
183
+ ### 🚀 Automatic Integration
184
+
185
+ The middleware is **automatically included** in all Django CFG projects:
186
+
187
+ ```python
188
+ class MyConfig(DjangoConfig):
189
+ # No configuration needed - PublicEndpointsMiddleware is always active
190
+ pass
191
+ ```
192
+
193
+ ### 🎯 Default Public Endpoints
194
+
195
+ The middleware protects these endpoints by default:
196
+
197
+ ```python
198
+ DEFAULT_PUBLIC_PATTERNS = [
199
+ r'^/api/accounts/otp/', # OTP endpoints (request, verify)
200
+ r'^/cfg/accounts/otp/', # CFG OTP endpoints
201
+ r'^/api/accounts/token/refresh/', # Token refresh
202
+ r'^/cfg/accounts/token/refresh/', # CFG Token refresh
203
+ r'^/api/health/', # Health check endpoints
204
+ r'^/cfg/api/health/', # CFG Health check endpoints
205
+ r'^/admin/login/', # Django admin login
206
+ r'^/api/schema/', # API schema endpoints
207
+ r'^/api/docs/', # API documentation
208
+ ]
209
+ ```
210
+
211
+ ### ⚙️ Custom Configuration
212
+
213
+ You can customize public endpoint patterns in your Django settings:
214
+
215
+ ```python
216
+ # settings.py (optional)
217
+ PUBLIC_ENDPOINT_PATTERNS = [
218
+ r'^/api/accounts/otp/',
219
+ r'^/api/public/',
220
+ r'^/api/webhooks/',
221
+ # Add your custom patterns here
222
+ ]
223
+ ```
224
+
225
+ ### 🔍 How It Works
226
+
227
+ 1. **Request Processing**: Middleware checks if the request path matches public endpoint patterns
228
+ 2. **Token Detection**: If a Bearer token is present, it's temporarily removed
229
+ 3. **Request Handling**: Django processes the request without authentication
230
+ 4. **Token Restoration**: The original Authorization header is restored after processing
231
+
232
+ ### 📊 Statistics
233
+
234
+ Get middleware statistics for monitoring:
235
+
236
+ ```python
237
+ from django_cfg.middleware import PublicEndpointsMiddleware
238
+
239
+ # In your view or management command
240
+ middleware = PublicEndpointsMiddleware()
241
+ stats = middleware.get_stats()
242
+
243
+ print(stats)
244
+ # {
245
+ # 'requests_processed': 1250,
246
+ # 'tokens_ignored': 45,
247
+ # 'public_endpoints_hit': 120,
248
+ # 'public_patterns_count': 9,
249
+ # 'middleware_active': True
250
+ # }
251
+ ```
252
+
253
+ ### 🔍 Logging
254
+
255
+ The middleware logs activity at DEBUG level:
256
+
257
+ ```python
258
+ # settings.py
259
+ LOGGING = {
260
+ 'loggers': {
261
+ 'django_cfg.middleware.public_endpoints': {
262
+ 'level': 'DEBUG',
263
+ 'handlers': ['console'],
264
+ },
265
+ },
266
+ }
267
+ ```
268
+
269
+ ### 🎛️ Manual Integration
270
+
271
+ If you need to include the middleware manually (not recommended):
272
+
273
+ ```python
274
+ # settings.py
275
+ MIDDLEWARE = [
276
+ 'django.middleware.security.SecurityMiddleware',
277
+ 'corsheaders.middleware.CorsMiddleware',
278
+ 'django_cfg.middleware.PublicEndpointsMiddleware', # Add early in stack
279
+ # ... other middleware
280
+ ]
281
+ ```
282
+
283
+ ### 🚨 Important Notes
284
+
285
+ 1. **Always Active**: Middleware is included by default in all Django CFG projects
286
+ 2. **Performance**: Uses compiled regex patterns for fast endpoint matching
287
+ 3. **Safety**: Only removes Authorization headers temporarily, restores them after processing
288
+ 4. **Logging**: All actions are logged for debugging and monitoring
289
+
290
+ ### 💡 Usage Examples
291
+
292
+ The middleware works automatically with no configuration needed:
293
+
294
+ ```python
295
+ # Your DjangoConfig
296
+ class MyProjectConfig(DjangoConfig):
297
+ # PublicEndpointsMiddleware is automatically active
298
+ pass
299
+
300
+ # These requests will work even with invalid tokens:
301
+ # POST /api/accounts/otp/request/ (with expired Bearer token)
302
+ # POST /cfg/accounts/otp/verify/ (with invalid Bearer token)
303
+ # GET /api/health/ (with any Bearer token)
304
+ ```
305
+
306
+ ### 🔧 Frontend Integration
307
+
308
+ Perfect companion to frontend error handling:
309
+
310
+ ```typescript
311
+ // Frontend automatically clears invalid tokens on 401/403
312
+ // Middleware ensures public endpoints work during token cleanup
313
+ const response = await api.requestOTP({
314
+ identifier: "user@example.com",
315
+ channel: "email"
316
+ });
317
+ // ✅ Works even if localStorage has invalid token
318
+ ```
@@ -5,7 +5,9 @@ Provides middleware components for Django CFG applications.
5
5
  """
6
6
 
7
7
  from .user_activity import UserActivityMiddleware
8
+ from .public_endpoints import PublicEndpointsMiddleware
8
9
 
9
10
  __all__ = [
10
11
  'UserActivityMiddleware',
12
+ 'PublicEndpointsMiddleware',
11
13
  ]
@@ -0,0 +1,182 @@
1
+ """
2
+ Public Endpoints Middleware
3
+
4
+ Middleware that ignores invalid JWT tokens on public endpoints to prevent
5
+ authentication errors on endpoints with AllowAny permissions.
6
+ """
7
+
8
+ import logging
9
+ import re
10
+ from typing import List, Optional, Set
11
+ from django.http import HttpRequest, HttpResponse
12
+ from django.utils.deprecation import MiddlewareMixin
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ class PublicEndpointsMiddleware(MiddlewareMixin):
18
+ """
19
+ Middleware that temporarily removes Authorization headers for public endpoints.
20
+
21
+ This prevents Django from trying to authenticate invalid JWT tokens on endpoints
22
+ that have AllowAny permissions, which can cause "User not found" errors.
23
+
24
+ Features:
25
+ - ✅ Configurable public endpoint patterns
26
+ - ✅ Smart JWT token detection
27
+ - ✅ Automatic restoration of headers after processing
28
+ - ✅ Detailed logging for debugging
29
+ - ✅ Performance optimized with compiled regex patterns
30
+ """
31
+
32
+ # Default public endpoint patterns
33
+ DEFAULT_PUBLIC_PATTERNS = [
34
+ r'^/api/accounts/otp/', # OTP endpoints (request, verify)
35
+ r'^/cfg/accounts/otp/', # CFG OTP endpoints
36
+ r'^/api/accounts/token/refresh/', # Token refresh
37
+ r'^/cfg/accounts/token/refresh/', # CFG Token refresh
38
+ r'^/api/health/', # Health check endpoints
39
+ r'^/cfg/api/health/', # CFG Health check endpoints
40
+ r'^/admin/login/', # Django admin login
41
+ r'^/api/schema/', # API schema endpoints
42
+ r'^/api/docs/', # API documentation
43
+ ]
44
+
45
+ def __init__(self, get_response=None):
46
+ super().__init__(get_response)
47
+ self.public_patterns: List[re.Pattern] = []
48
+ self.stats = {
49
+ 'requests_processed': 0,
50
+ 'tokens_ignored': 0,
51
+ 'public_endpoints_hit': 0,
52
+ }
53
+ self._compile_patterns()
54
+
55
+ def _compile_patterns(self):
56
+ """Compile regex patterns for better performance."""
57
+ patterns = self._get_public_patterns()
58
+ self.public_patterns = [re.compile(pattern) for pattern in patterns]
59
+ logger.debug(f"Compiled {len(self.public_patterns)} public endpoint patterns")
60
+
61
+ def _get_public_patterns(self) -> List[str]:
62
+ """Get public endpoint patterns from Django settings or use defaults."""
63
+ from django.conf import settings
64
+
65
+ # Try to get patterns from settings
66
+ custom_patterns = getattr(settings, 'PUBLIC_ENDPOINT_PATTERNS', None)
67
+ if custom_patterns:
68
+ logger.debug(f"Using custom public patterns: {len(custom_patterns)} patterns")
69
+ return custom_patterns
70
+
71
+ # Use defaults
72
+ logger.debug(f"Using default public patterns: {len(self.DEFAULT_PUBLIC_PATTERNS)} patterns")
73
+ return self.DEFAULT_PUBLIC_PATTERNS
74
+
75
+ def _is_public_endpoint(self, path: str) -> bool:
76
+ """Check if the request path matches any public endpoint pattern."""
77
+ for pattern in self.public_patterns:
78
+ if pattern.match(path):
79
+ return True
80
+ return False
81
+
82
+ def _has_jwt_token(self, request: HttpRequest) -> bool:
83
+ """Check if request has a JWT Authorization header."""
84
+ auth_header = request.META.get('HTTP_AUTHORIZATION', '')
85
+ return auth_header.startswith('Bearer ')
86
+
87
+ def _extract_auth_header(self, request: HttpRequest) -> Optional[str]:
88
+ """Extract and remove Authorization header from request."""
89
+ return request.META.pop('HTTP_AUTHORIZATION', None)
90
+
91
+ def _restore_auth_header(self, request: HttpRequest, auth_header: str):
92
+ """Restore Authorization header to request."""
93
+ if auth_header:
94
+ request.META['HTTP_AUTHORIZATION'] = auth_header
95
+
96
+ def process_request(self, request: HttpRequest) -> Optional[HttpResponse]:
97
+ """
98
+ Process incoming request and temporarily remove auth header for public endpoints.
99
+ """
100
+ self.stats['requests_processed'] += 1
101
+
102
+ # Check if this is a public endpoint
103
+ if not self._is_public_endpoint(request.path):
104
+ return None
105
+
106
+ self.stats['public_endpoints_hit'] += 1
107
+
108
+ # Check if request has JWT token
109
+ if not self._has_jwt_token(request):
110
+ return None
111
+
112
+ # Store the auth header and remove it temporarily
113
+ auth_header = self._extract_auth_header(request)
114
+ if auth_header:
115
+ self.stats['tokens_ignored'] += 1
116
+ # Store in request for restoration later
117
+ request._original_auth_header = auth_header
118
+
119
+ logger.debug(
120
+ f"Temporarily removed auth header for public endpoint: {request.path}",
121
+ extra={
122
+ 'path': request.path,
123
+ 'method': request.method,
124
+ 'has_token': bool(auth_header),
125
+ }
126
+ )
127
+
128
+ return None
129
+
130
+ def process_response(self, request: HttpRequest, response: HttpResponse) -> HttpResponse:
131
+ """
132
+ Restore Authorization header after request processing.
133
+ """
134
+ # Restore auth header if it was temporarily removed
135
+ if hasattr(request, '_original_auth_header'):
136
+ self._restore_auth_header(request, request._original_auth_header)
137
+ delattr(request, '_original_auth_header')
138
+
139
+ logger.debug(
140
+ f"Restored auth header for public endpoint: {request.path}",
141
+ extra={
142
+ 'path': request.path,
143
+ 'status_code': response.status_code,
144
+ }
145
+ )
146
+
147
+ return response
148
+
149
+ def get_stats(self) -> dict:
150
+ """Get middleware statistics."""
151
+ return {
152
+ **self.stats,
153
+ 'public_patterns_count': len(self.public_patterns),
154
+ 'middleware_active': True,
155
+ }
156
+
157
+ def reset_stats(self):
158
+ """Reset middleware statistics."""
159
+ self.stats = {
160
+ 'requests_processed': 0,
161
+ 'tokens_ignored': 0,
162
+ 'public_endpoints_hit': 0,
163
+ }
164
+ logger.info("PublicEndpointsMiddleware stats reset")
165
+
166
+
167
+ # Convenience function for getting middleware stats
168
+ def get_public_endpoints_stats() -> dict:
169
+ """Get statistics from PublicEndpointsMiddleware if available."""
170
+ try:
171
+ # This would need to be implemented if we want global stats access
172
+ # For now, return basic info
173
+ return {
174
+ 'middleware_available': True,
175
+ 'note': 'Use middleware.get_stats() method for detailed statistics'
176
+ }
177
+ except Exception as e:
178
+ logger.error(f"Error getting public endpoints stats: {e}")
179
+ return {
180
+ 'middleware_available': False,
181
+ 'error': str(e)
182
+ }