django-cfg 1.1.74__tar.gz → 1.1.76__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.
- {django_cfg-1.1.74 → django_cfg-1.1.76}/PKG-INFO +1 -1
- {django_cfg-1.1.74 → django_cfg-1.1.76}/pyproject.toml +1 -1
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/__init__.py +1 -1
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/core/config.py +1 -0
- django_cfg-1.1.76/src/django_cfg/middleware/README.md +318 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/middleware/__init__.py +2 -0
- django_cfg-1.1.76/src/django_cfg/middleware/public_endpoints.py +182 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/modules/django_llm/llm/client.py +38 -26
- django_cfg-1.1.76/src/django_cfg/modules/django_llm/llm/models.py +114 -0
- django_cfg-1.1.74/src/django_cfg/middleware/README.md +0 -160
- {django_cfg-1.1.74 → django_cfg-1.1.76}/.gitignore +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/LICENSE +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/MANIFEST.in +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/README.md +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/requirements-dev.txt +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/requirements-test.txt +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/requirements.txt +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/README.md +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/__init__.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/accounts/README.md +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/accounts/__init__.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/accounts/admin/__init__.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/accounts/admin/activity.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/accounts/admin/filters.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/accounts/admin/group.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/accounts/admin/inlines.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/accounts/admin/otp.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/accounts/admin/registration_source.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/accounts/admin/resources.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/accounts/admin/twilio_response.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/accounts/admin/user.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/accounts/apps.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/accounts/management/commands/test_otp.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/accounts/managers/__init__.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/accounts/managers/user_manager.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/accounts/migrations/0001_initial.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/accounts/migrations/0002_add_phone_otp_clean.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/accounts/migrations/0003_twilioresponse.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/accounts/migrations/0004_delete_twilioresponse.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/accounts/migrations/0005_twilioresponse.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/accounts/migrations/__init__.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/accounts/models.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/accounts/serializers/__init__.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/accounts/serializers/otp.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/accounts/serializers/profile.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/accounts/serializers/webhook.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/accounts/services/__init__.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/accounts/services/activity_service.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/accounts/services/otp_service.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/accounts/signals.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/accounts/templates/emails/base_email.html +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/accounts/templates/emails/base_email.txt +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/accounts/templates/emails/otp_email.html +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/accounts/templates/emails/otp_email.txt +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/accounts/templates/emails/welcome_email.html +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/accounts/templates/emails/welcome_email.txt +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/accounts/urls.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/accounts/utils/notifications.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/accounts/views/__init__.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/accounts/views/otp.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/accounts/views/profile.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/accounts/views/webhook.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/api/__init__.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/api/commands/__init__.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/api/commands/urls.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/api/commands/views.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/api/health/__init__.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/api/health/urls.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/api/health/views.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/leads/README.md +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/leads/__init__.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/leads/admin/__init__.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/leads/admin/leads_admin.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/leads/admin/resources.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/leads/apps.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/leads/migrations/0001_initial.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/leads/migrations/__init__.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/leads/models.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/leads/serializers.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/leads/signals.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/leads/tests.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/leads/urls.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/leads/views.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/newsletter/README.md +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/newsletter/__init__.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/newsletter/admin/__init__.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/newsletter/admin/filters.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/newsletter/admin/newsletter_admin.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/newsletter/admin/resources.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/newsletter/apps.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/newsletter/management/__init__.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/newsletter/management/commands/__init__.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/newsletter/management/commands/test_newsletter.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/newsletter/managers/README.md +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/newsletter/managers/__init__.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/newsletter/migrations/0001_initial.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/newsletter/migrations/__init__.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/newsletter/models.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/newsletter/serializers.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/newsletter/services/email_service.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/newsletter/signals.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/newsletter/urls.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/newsletter/utils/__init__.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/newsletter/views/__init__.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/newsletter/views/campaigns.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/newsletter/views/emails.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/newsletter/views/newsletters.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/newsletter/views/subscriptions.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/newsletter/views/tracking.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/support/__init__.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/support/admin/__init__.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/support/admin/filters.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/support/admin/resources.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/support/admin/support_admin.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/support/apps.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/support/managers/message_manager.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/support/managers/ticket_manager.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/support/migrations/0001_initial.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/support/migrations/0002_alter_message_ticket.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/support/migrations/__init__.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/support/models.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/support/serializers.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/support/signals.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/support/templates/support/chat/access_denied.html +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/support/templates/support/chat/ticket_chat.html +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/support/urls.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/support/utils/__init__.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/support/utils/support_email_service.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/support/views/__init__.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/support/views/admin.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/support/views/api.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/support/views/chat.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/tasks/__init__.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/tasks/admin.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/tasks/apps.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/tasks/serializers.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/tasks/static/tasks/css/dashboard.css +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/tasks/static/tasks/js/api.js +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/tasks/static/tasks/js/dashboard.js +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/tasks/static/tasks/js/modals.js +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/tasks/static/tasks/js/notifications.js +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/tasks/static/tasks/js/task-monitor.js +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/tasks/static/tasks/js/theme.js +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/tasks/templates/tasks/base.html +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/tasks/templates/tasks/components/info_cards.html +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/tasks/templates/tasks/components/management_actions.html +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/tasks/templates/tasks/components/overview_tab.html +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/tasks/templates/tasks/components/queues_tab.html +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/tasks/templates/tasks/components/status_cards.html +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/tasks/templates/tasks/components/tab_navigation.html +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/tasks/templates/tasks/components/task_details_modal.html +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/tasks/templates/tasks/components/tasks_tab.html +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/tasks/templates/tasks/components/workers_tab.html +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/tasks/templates/tasks/dashboard.html +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/tasks/urls.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/tasks/views.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps/urls.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/apps.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/archive/django_sample.zip +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/cli/README.md +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/cli/__init__.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/cli/commands/__init__.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/cli/commands/create_project.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/cli/commands/info.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/cli/main.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/cli/utils.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/core/__init__.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/core/environment.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/core/generation.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/core/validation.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/exceptions.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/integration.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/management/__init__.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/management/commands/__init__.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/management/commands/check_settings.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/management/commands/clear_constance.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/management/commands/create_token.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/management/commands/generate.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/management/commands/list_urls.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/management/commands/migrate_all.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/management/commands/migrator.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/management/commands/rundramatiq.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/management/commands/runserver_ngrok.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/management/commands/script.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/management/commands/show_config.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/management/commands/show_urls.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/management/commands/superuser.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/management/commands/task_clear.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/management/commands/task_status.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/management/commands/test_email.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/management/commands/test_telegram.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/management/commands/test_twilio.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/management/commands/tree.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/management/commands/validate_config.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/middleware/user_activity.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/models/__init__.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/models/cache.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/models/constance.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/models/database.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/models/drf.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/models/jwt.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/models/limits.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/models/ngrok.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/models/revolution.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/models/services.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/models/tasks.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/models/unfold.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/modules/__init__.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/modules/base.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/modules/django_currency/README.md +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/modules/django_currency/__init__.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/modules/django_currency/cache.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/modules/django_currency/converter.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/modules/django_currency/service.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/modules/django_email.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/modules/django_llm/README.md +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/modules/django_llm/__init__.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/modules/django_llm/example.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/modules/django_llm/llm/__init__.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/modules/django_llm/llm/cache.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/modules/django_llm/llm/costs.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/modules/django_llm/llm/extractor.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/modules/django_llm/llm/models_cache.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/modules/django_llm/llm/tokenizer.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/modules/django_llm/translator/__init__.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/modules/django_llm/translator/cache.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/modules/django_llm/translator/translator.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/modules/django_logger.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/modules/django_ngrok.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/modules/django_tasks.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/modules/django_telegram.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/modules/django_twilio/README.md +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/modules/django_twilio/__init__.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/modules/django_twilio/exceptions.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/modules/django_twilio/models.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/modules/django_twilio/sendgrid_service.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/modules/django_twilio/service.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/modules/django_twilio/simple_service.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/modules/django_twilio/templates/guide.md +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/modules/django_twilio/templates/sendgrid_otp_email.html +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/modules/django_twilio/templates/sendgrid_test_data.json +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/modules/django_twilio/twilio_service.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/modules/dramatiq_setup.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/modules/logger.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/modules/unfold/__init__.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/modules/unfold/callbacks.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/modules/unfold/dashboard.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/modules/unfold/models.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/modules/unfold/system_monitor.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/modules/unfold/tailwind.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/pyproject.toml +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/routers.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/templates/__init__.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/templates/admin/index.html +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/templates/admin/layouts/dashboard_with_tabs.html +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/templates/admin/snippets/components/activity_tracker.html +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/templates/admin/snippets/components/charts_section.html +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/templates/admin/snippets/components/django_commands.html +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/templates/admin/snippets/components/quick_actions.html +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/templates/admin/snippets/components/recent_activity.html +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/templates/admin/snippets/components/recent_users_table.html +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/templates/admin/snippets/components/stats_cards.html +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/templates/admin/snippets/components/stats_tiles.html +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/templates/admin/snippets/components/system_health.html +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/templates/admin/snippets/components/system_metrics.html +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/templates/admin/snippets/components/user_permissions.html +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/templates/admin/snippets/tabs/app_stats_tab.html +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/templates/admin/snippets/tabs/commands_tab.html +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/templates/admin/snippets/tabs/overview_tab.html +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/templates/admin/snippets/tabs/stats_tab.html +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/templates/admin/snippets/tabs/users_tab.html +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/templates/admin/snippets/zones/zones_table.html +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/templates/emails/base_email.html +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/utils/__init__.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/utils/path_resolution.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/src/django_cfg/utils/smart_defaults.py +0 -0
- {django_cfg-1.1.74 → django_cfg-1.1.76}/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.
|
3
|
+
Version: 1.1.76
|
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.
|
7
|
+
version = "1.1.76"
|
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.
|
41
|
+
__version__ = "1.1.76"
|
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
|
+
```
|
@@ -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
|
+
}
|
@@ -26,6 +26,17 @@ from .models_cache import ModelsCache, ModelInfo
|
|
26
26
|
from .costs import calculate_chat_cost, calculate_embedding_cost, estimate_cost
|
27
27
|
from .tokenizer import Tokenizer
|
28
28
|
from .extractor import JSONExtractor
|
29
|
+
from .models import (
|
30
|
+
EmbeddingResponse,
|
31
|
+
ChatCompletionResponse,
|
32
|
+
TokenUsage,
|
33
|
+
ChatChoice,
|
34
|
+
LLMStats,
|
35
|
+
CostEstimate,
|
36
|
+
ValidationResult,
|
37
|
+
CacheInfo,
|
38
|
+
LLMError
|
39
|
+
)
|
29
40
|
|
30
41
|
logger = logging.getLogger(__name__)
|
31
42
|
|
@@ -458,7 +469,7 @@ class LLMClient:
|
|
458
469
|
self.models_cache.clear_cache()
|
459
470
|
logger.info("LLM client cache cleared")
|
460
471
|
|
461
|
-
def generate_embedding(self, text: str, model: str = "text-embedding-ada-002") ->
|
472
|
+
def generate_embedding(self, text: str, model: str = "text-embedding-ada-002") -> EmbeddingResponse:
|
462
473
|
"""
|
463
474
|
Generate embedding for text.
|
464
475
|
|
@@ -484,7 +495,8 @@ class LLMClient:
|
|
484
495
|
if cached_response:
|
485
496
|
logger.debug("Cache hit for embedding generation")
|
486
497
|
self.stats['cache_hits'] += 1
|
487
|
-
|
498
|
+
# Convert cached dict back to Pydantic model
|
499
|
+
return EmbeddingResponse(**cached_response)
|
488
500
|
|
489
501
|
self.stats['cache_misses'] += 1
|
490
502
|
self.stats['total_requests'] += 1
|
@@ -526,16 +538,16 @@ class LLMClient:
|
|
526
538
|
tokens_used = len(text.split()) # Rough estimate
|
527
539
|
cost = calculate_embedding_cost(tokens_used, model, self.models_cache)
|
528
540
|
|
529
|
-
result =
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
541
|
+
result = EmbeddingResponse(
|
542
|
+
embedding=mock_embedding,
|
543
|
+
tokens=tokens_used,
|
544
|
+
cost=cost,
|
545
|
+
model=model,
|
546
|
+
text_length=len(text),
|
547
|
+
dimension=len(mock_embedding),
|
548
|
+
response_time=time.time() - start_time,
|
549
|
+
warning="This is a mock embedding, not a real one. OpenRouter doesn't support embedding models."
|
550
|
+
)
|
539
551
|
else:
|
540
552
|
# Use real OpenAI embedding API
|
541
553
|
response = self.client.embeddings.create(
|
@@ -551,25 +563,25 @@ class LLMClient:
|
|
551
563
|
tokens_used = response.usage.total_tokens
|
552
564
|
cost = calculate_embedding_cost(tokens_used, model, self.models_cache)
|
553
565
|
|
554
|
-
result =
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
566
|
+
result = EmbeddingResponse(
|
567
|
+
embedding=embedding_vector,
|
568
|
+
tokens=tokens_used,
|
569
|
+
cost=cost,
|
570
|
+
model=model,
|
571
|
+
text_length=len(text),
|
572
|
+
dimension=len(embedding_vector),
|
573
|
+
response_time=time.time() - start_time
|
574
|
+
)
|
563
575
|
|
564
576
|
# Update statistics
|
565
577
|
self.stats['successful_requests'] += 1
|
566
|
-
self.stats['total_tokens_used'] += result
|
567
|
-
self.stats['total_cost_usd'] += result
|
578
|
+
self.stats['total_tokens_used'] += result.tokens
|
579
|
+
self.stats['total_cost_usd'] += result.cost
|
568
580
|
|
569
|
-
# Cache the result
|
570
|
-
self.cache.set_response(request_hash, result, model)
|
581
|
+
# Cache the result (convert to dict for caching)
|
582
|
+
self.cache.set_response(request_hash, result.model_dump(), model)
|
571
583
|
|
572
|
-
logger.debug(f"Generated embedding: {result
|
584
|
+
logger.debug(f"Generated embedding: {result.tokens} tokens, ${result.cost:.6f}")
|
573
585
|
return result
|
574
586
|
|
575
587
|
except Exception as e:
|