django-cfg 1.1.52__tar.gz → 1.1.53__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.52 → django_cfg-1.1.53}/PKG-INFO +1 -1
- {django_cfg-1.1.52 → django_cfg-1.1.53}/pyproject.toml +1 -1
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/__init__.py +3 -3
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/accounts/admin/__init__.py +0 -2
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/accounts/admin/filters.py +0 -54
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/accounts/admin/otp.py +1 -12
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/accounts/managers/user_manager.py +0 -16
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/accounts/models.py +0 -93
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/accounts/serializers/otp.py +2 -2
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/accounts/services/otp_service.py +25 -121
- django_cfg-1.1.53/src/django_cfg/apps/accounts/templates/emails/otp_email.html +94 -0
- django_cfg-1.1.53/src/django_cfg/apps/accounts/templates/emails/otp_email.txt +16 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/accounts/urls.py +1 -2
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/accounts/utils/notifications.py +32 -184
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/accounts/views/__init__.py +0 -3
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/accounts/views/otp.py +0 -8
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/api/commands/urls.py +0 -2
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/api/health/urls.py +0 -2
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/tasks/urls.py +1 -1
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/archive/django_sample.zip +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/cli/README.md +1 -1
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/cli/commands/create_project.py +2 -2
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/cli/commands/info.py +1 -1
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/core/config.py +0 -6
- django_cfg-1.1.53/src/django_cfg/examples/README_NGROK.md +186 -0
- django_cfg-1.1.53/src/django_cfg/examples/README_NGROK_ENV.md +194 -0
- django_cfg-1.1.53/src/django_cfg/examples/ngrok_env_example.py +155 -0
- django_cfg-1.1.53/src/django_cfg/examples/ngrok_example.py +75 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/management/commands/rundramatiq.py +1 -2
- django_cfg-1.1.53/src/django_cfg/management/commands/show_urls.py +341 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/management/commands/test_email.py +1 -1
- django_cfg-1.1.53/src/django_cfg/management/commands/test_twilio.py +101 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/modules/django_llm/llm/client.py +2 -2
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/modules/django_twilio/README.md +1 -1
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/modules/django_twilio/__init__.py +37 -50
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/modules/django_twilio/models.py +16 -34
- django_cfg-1.1.53/src/django_cfg/modules/django_twilio/service.py +942 -0
- django_cfg-1.1.53/src/django_cfg/modules/django_twilio/simple_service.py +290 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/modules/unfold/dashboard.py +1 -45
- django_cfg-1.1.52/src/django_cfg/apps/accounts/templates/emails/otp_email.html +0 -213
- django_cfg-1.1.52/src/django_cfg/apps/accounts/templates/emails/otp_email.txt +0 -29
- django_cfg-1.1.52/src/django_cfg/management/commands/show_urls.py +0 -302
- django_cfg-1.1.52/src/django_cfg/management/commands/test_twilio.py +0 -614
- {django_cfg-1.1.52 → django_cfg-1.1.53}/.gitignore +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/LICENSE +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/MANIFEST.in +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/README.md +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/requirements-dev.txt +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/requirements-test.txt +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/requirements.txt +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/__init__.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/accounts/README.md +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/accounts/__init__.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/accounts/admin/activity.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/accounts/admin/group.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/accounts/admin/inlines.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/accounts/admin/registration_source.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/accounts/admin/twilio_response.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/accounts/admin/user.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/accounts/apps.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/accounts/management/commands/test_otp.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/accounts/managers/__init__.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/accounts/migrations/0001_initial.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/accounts/migrations/0002_add_phone_otp_clean.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/accounts/migrations/0003_twilioresponse.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/accounts/migrations/__init__.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/accounts/serializers/__init__.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/accounts/serializers/profile.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/accounts/serializers/webhook.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/accounts/services/__init__.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/accounts/services/activity_service.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/accounts/signals.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/accounts/templates/emails/base_email.html +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/accounts/templates/emails/base_email.txt +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/accounts/templates/emails/welcome_email.html +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/accounts/templates/emails/welcome_email.txt +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/accounts/views/profile.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/accounts/views/webhook.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/api/__init__.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/api/commands/__init__.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/api/commands/views.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/api/health/__init__.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/api/health/views.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/leads/README.md +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/leads/__init__.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/leads/admin.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/leads/apps.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/leads/migrations/0001_initial.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/leads/migrations/__init__.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/leads/models.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/leads/serializers.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/leads/signals.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/leads/tests.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/leads/urls.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/leads/views.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/newsletter/README.md +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/newsletter/__init__.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/newsletter/admin.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/newsletter/admin_filters.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/newsletter/apps.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/newsletter/management/__init__.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/newsletter/management/commands/__init__.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/newsletter/management/commands/test_newsletter.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/newsletter/managers/README.md +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/newsletter/managers/__init__.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/newsletter/migrations/0001_initial.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/newsletter/migrations/__init__.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/newsletter/models.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/newsletter/serializers.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/newsletter/services/email_service.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/newsletter/signals.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/newsletter/urls.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/newsletter/utils/__init__.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/newsletter/views/__init__.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/newsletter/views/campaigns.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/newsletter/views/emails.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/newsletter/views/newsletters.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/newsletter/views/subscriptions.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/newsletter/views/tracking.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/support/__init__.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/support/admin.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/support/admin_filters.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/support/apps.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/support/managers/message_manager.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/support/managers/ticket_manager.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/support/migrations/0001_initial.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/support/migrations/0002_alter_message_ticket.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/support/migrations/__init__.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/support/models.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/support/serializers.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/support/signals.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/support/templates/support/chat/access_denied.html +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/support/templates/support/chat/ticket_chat.html +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/support/urls.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/support/utils/__init__.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/support/utils/support_email_service.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/support/views/__init__.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/support/views/admin.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/support/views/api.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/support/views/chat.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/tasks/__init__.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/tasks/admin.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/tasks/apps.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/tasks/templates/tasks/dashboard.html +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/tasks/views.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/urls.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/cli/__init__.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/cli/commands/__init__.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/cli/main.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/cli/utils.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/core/__init__.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/core/environment.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/core/generation.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/core/validation.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/exceptions.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/integration.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/management/__init__.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/management/commands/__init__.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/management/commands/check_settings.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/management/commands/create_token.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/management/commands/generate.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/management/commands/list_urls.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/management/commands/migrator.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/management/commands/runserver_ngrok.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/management/commands/script.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/management/commands/show_config.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/management/commands/superuser.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/management/commands/task_clear.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/management/commands/task_status.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/management/commands/test_telegram.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/management/commands/tree.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/management/commands/validate_config.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/middleware/README.md +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/middleware/__init__.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/middleware/user_activity.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/models/__init__.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/models/cache.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/models/constance.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/models/database.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/models/drf.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/models/jwt.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/models/limits.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/models/ngrok.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/models/revolution.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/models/services.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/models/tasks.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/models/unfold.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/modules/__init__.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/modules/base.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/modules/django_currency/README.md +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/modules/django_currency/__init__.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/modules/django_currency/cache.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/modules/django_currency/converter.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/modules/django_currency/service.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/modules/django_email.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/modules/django_llm/README.md +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/modules/django_llm/__init__.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/modules/django_llm/example.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/modules/django_llm/llm/__init__.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/modules/django_llm/llm/cache.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/modules/django_llm/llm/models_cache.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/modules/django_llm/service.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/modules/django_llm/translator/__init__.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/modules/django_llm/translator/cache.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/modules/django_llm/translator/translator.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/modules/django_logger.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/modules/django_ngrok.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/modules/django_tasks.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/modules/django_telegram.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/modules/django_twilio/exceptions.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/modules/django_twilio/sendgrid_service.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/modules/django_twilio/templates/guide.md +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/modules/django_twilio/templates/sendgrid_otp_email.html +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/modules/django_twilio/templates/sendgrid_test_data.json +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/modules/django_twilio/twilio_service.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/modules/logger.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/modules/unfold/__init__.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/modules/unfold/callbacks.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/modules/unfold/models.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/modules/unfold/system_monitor.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/modules/unfold/tailwind.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/routers.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/templates/__init__.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/templates/admin/index.html +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/templates/admin/layouts/dashboard_with_tabs.html +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/templates/admin/snippets/components/activity_tracker.html +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/templates/admin/snippets/components/charts_section.html +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/templates/admin/snippets/components/django_commands.html +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/templates/admin/snippets/components/quick_actions.html +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/templates/admin/snippets/components/recent_activity.html +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/templates/admin/snippets/components/recent_users_table.html +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/templates/admin/snippets/components/stats_cards.html +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/templates/admin/snippets/components/stats_tiles.html +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/templates/admin/snippets/components/system_health.html +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/templates/admin/snippets/components/system_metrics.html +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/templates/admin/snippets/components/user_permissions.html +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/templates/admin/snippets/tabs/app_stats_tab.html +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/templates/admin/snippets/tabs/commands_tab.html +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/templates/admin/snippets/tabs/overview_tab.html +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/templates/admin/snippets/tabs/stats_tab.html +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/templates/admin/snippets/tabs/users_tab.html +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/templates/admin/snippets/zones/zones_table.html +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/templates/emails/base_email.html +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/utils/__init__.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/utils/path_resolution.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/utils/smart_defaults.py +0 -0
- {django_cfg-1.1.52 → django_cfg-1.1.53}/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.53
|
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.53"
|
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,9 +38,9 @@ default_app_config = "django_cfg.apps.DjangoCfgConfig"
|
|
38
38
|
from typing import TYPE_CHECKING
|
39
39
|
|
40
40
|
# Version information
|
41
|
-
__version__ = "1.1.
|
42
|
-
__author__ = "
|
43
|
-
__email__ = "info@
|
41
|
+
__version__ = "1.1.53"
|
42
|
+
__author__ = "Unrealos Team"
|
43
|
+
__email__ = "info@unrealos.com"
|
44
44
|
__license__ = "MIT"
|
45
45
|
|
46
46
|
# Core exports - only import when needed to avoid circular imports
|
@@ -7,7 +7,6 @@ from .otp import OTPSecretAdmin
|
|
7
7
|
from .registration_source import RegistrationSourceAdmin, UserRegistrationSourceAdmin
|
8
8
|
from .activity import UserActivityAdmin
|
9
9
|
from .group import GroupAdmin
|
10
|
-
from .twilio_response import TwilioResponseAdmin
|
11
10
|
|
12
11
|
__all__ = [
|
13
12
|
'CustomUserAdmin',
|
@@ -16,5 +15,4 @@ __all__ = [
|
|
16
15
|
'UserRegistrationSourceAdmin',
|
17
16
|
'UserActivityAdmin',
|
18
17
|
'GroupAdmin',
|
19
|
-
'TwilioResponseAdmin',
|
20
18
|
]
|
@@ -96,57 +96,3 @@ class ActivityTypeFilter(admin.SimpleListFilter):
|
|
96
96
|
elif self.value():
|
97
97
|
return queryset.filter(activity_type=self.value())
|
98
98
|
return queryset
|
99
|
-
|
100
|
-
|
101
|
-
class TwilioResponseStatusFilter(admin.SimpleListFilter):
|
102
|
-
title = "Response Status"
|
103
|
-
parameter_name = "twilio_status"
|
104
|
-
|
105
|
-
def lookups(self, request, model_admin):
|
106
|
-
return (
|
107
|
-
("successful", "Successful"),
|
108
|
-
("failed", "Failed"),
|
109
|
-
("pending", "Pending"),
|
110
|
-
("with_errors", "With Errors"),
|
111
|
-
("recent", "Recent (24h)"),
|
112
|
-
)
|
113
|
-
|
114
|
-
def queryset(self, request, queryset):
|
115
|
-
now = timezone.now()
|
116
|
-
if self.value() == "successful":
|
117
|
-
return queryset.filter(
|
118
|
-
status__in=['sent', 'delivered', 'approved'],
|
119
|
-
error_code__isnull=True
|
120
|
-
)
|
121
|
-
elif self.value() == "failed":
|
122
|
-
return queryset.filter(
|
123
|
-
status__in=['failed', 'undelivered', 'rejected']
|
124
|
-
)
|
125
|
-
elif self.value() == "pending":
|
126
|
-
return queryset.filter(status='pending')
|
127
|
-
elif self.value() == "with_errors":
|
128
|
-
return queryset.exclude(error_code__isnull=True)
|
129
|
-
elif self.value() == "recent":
|
130
|
-
return queryset.filter(created_at__gte=now - timedelta(hours=24))
|
131
|
-
return queryset
|
132
|
-
|
133
|
-
|
134
|
-
class TwilioResponseTypeFilter(admin.SimpleListFilter):
|
135
|
-
title = "Response Type"
|
136
|
-
parameter_name = "twilio_response_type"
|
137
|
-
|
138
|
-
def lookups(self, request, model_admin):
|
139
|
-
return (
|
140
|
-
("api_send", "API Send"),
|
141
|
-
("api_verify", "API Verify"),
|
142
|
-
("webhook_status", "Webhook Status"),
|
143
|
-
("webhook_delivery", "Webhook Delivery"),
|
144
|
-
("otp_related", "OTP Related"),
|
145
|
-
)
|
146
|
-
|
147
|
-
def queryset(self, request, queryset):
|
148
|
-
if self.value() == "otp_related":
|
149
|
-
return queryset.filter(otp_secret__isnull=False)
|
150
|
-
elif self.value():
|
151
|
-
return queryset.filter(response_type=self.value())
|
152
|
-
return queryset
|
@@ -8,18 +8,16 @@ from unfold.admin import ModelAdmin
|
|
8
8
|
|
9
9
|
from ..models import OTPSecret
|
10
10
|
from .filters import OTPStatusFilter
|
11
|
-
from .twilio_response import TwilioResponseInline
|
12
11
|
|
13
12
|
|
14
13
|
@admin.register(OTPSecret)
|
15
14
|
class OTPSecretAdmin(ModelAdmin):
|
16
|
-
list_display = ["recipient", "channel_type", "secret", "status", "
|
15
|
+
list_display = ["recipient", "channel_type", "secret", "status", "created", "expires"]
|
17
16
|
list_display_links = ["recipient", "secret"]
|
18
17
|
list_filter = [OTPStatusFilter, "channel_type", "is_used", "created_at"]
|
19
18
|
search_fields = ["recipient", "secret"]
|
20
19
|
readonly_fields = ["created_at", "expires_at"]
|
21
20
|
ordering = ["-created_at"]
|
22
|
-
inlines = [TwilioResponseInline]
|
23
21
|
|
24
22
|
fieldsets = (
|
25
23
|
(
|
@@ -59,12 +57,3 @@ class OTPSecretAdmin(ModelAdmin):
|
|
59
57
|
return naturaltime(obj.expires_at)
|
60
58
|
|
61
59
|
expires.short_description = "Expires"
|
62
|
-
|
63
|
-
def twilio_responses_count(self, obj):
|
64
|
-
"""Count of related Twilio responses."""
|
65
|
-
count = obj.twilio_responses.count()
|
66
|
-
if count == 0:
|
67
|
-
return "—"
|
68
|
-
return f"{count} response{'s' if count != 1 else ''}"
|
69
|
-
|
70
|
-
twilio_responses_count.short_description = "Twilio"
|
{django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/accounts/managers/user_manager.py
RENAMED
@@ -246,11 +246,6 @@ class UserManager(UserManager):
|
|
246
246
|
return user.first_name
|
247
247
|
elif user.last_name:
|
248
248
|
return user.last_name
|
249
|
-
|
250
|
-
# For phone users with temp email, use display_username instead
|
251
|
-
if user.email and user.email.startswith('phone_') and '@temp.' in user.email:
|
252
|
-
return self.get_display_username(user)
|
253
|
-
|
254
249
|
return user.email
|
255
250
|
|
256
251
|
def get_initials(self, user) -> str:
|
@@ -267,17 +262,6 @@ class UserManager(UserManager):
|
|
267
262
|
return user.first_name[0].upper()
|
268
263
|
elif user.last_name:
|
269
264
|
return user.last_name[0].upper()
|
270
|
-
|
271
|
-
# For phone users with temp email, use username initials instead
|
272
|
-
if user.email and user.email.startswith('phone_') and '@temp.' in user.email:
|
273
|
-
if user.username:
|
274
|
-
# Take first two characters of username
|
275
|
-
clean_username = user.username.replace("_", "").replace("-", "").replace(".", "")
|
276
|
-
if len(clean_username) >= 2:
|
277
|
-
return f"{clean_username[0]}{clean_username[1]}".upper()
|
278
|
-
elif len(clean_username) == 1:
|
279
|
-
return clean_username[0].upper()
|
280
|
-
|
281
265
|
return user.email[0].upper()
|
282
266
|
|
283
267
|
def get_display_username(self, user) -> str:
|
@@ -203,99 +203,6 @@ class OTPSecret(models.Model):
|
|
203
203
|
]
|
204
204
|
|
205
205
|
|
206
|
-
class TwilioResponse(models.Model):
|
207
|
-
"""
|
208
|
-
Store Twilio API responses and webhook events.
|
209
|
-
|
210
|
-
This model tracks all interactions with Twilio including:
|
211
|
-
- API responses from sending messages/OTP
|
212
|
-
- Webhook events for delivery status updates
|
213
|
-
- Error tracking and debugging information
|
214
|
-
"""
|
215
|
-
|
216
|
-
RESPONSE_TYPES = [
|
217
|
-
('api_send', 'API Send Request'),
|
218
|
-
('api_verify', 'API Verify Request'),
|
219
|
-
('webhook_status', 'Webhook Status Update'),
|
220
|
-
('webhook_delivery', 'Webhook Delivery Report'),
|
221
|
-
]
|
222
|
-
|
223
|
-
SERVICE_TYPES = [
|
224
|
-
('whatsapp', 'WhatsApp'),
|
225
|
-
('sms', 'SMS'),
|
226
|
-
('voice', 'Voice'),
|
227
|
-
('email', 'Email'),
|
228
|
-
('verify', 'Verify API'),
|
229
|
-
]
|
230
|
-
|
231
|
-
# Basic info
|
232
|
-
response_type = models.CharField(max_length=20, choices=RESPONSE_TYPES)
|
233
|
-
service_type = models.CharField(max_length=10, choices=SERVICE_TYPES)
|
234
|
-
|
235
|
-
# Twilio identifiers
|
236
|
-
message_sid = models.CharField(max_length=34, blank=True, help_text="Twilio Message SID")
|
237
|
-
verification_sid = models.CharField(max_length=34, blank=True, help_text="Twilio Verification SID")
|
238
|
-
|
239
|
-
# Request/Response data
|
240
|
-
request_data = models.JSONField(default=dict, help_text="Original request parameters")
|
241
|
-
response_data = models.JSONField(default=dict, help_text="Twilio API response")
|
242
|
-
|
243
|
-
# Status tracking
|
244
|
-
status = models.CharField(max_length=20, blank=True, help_text="Message/Verification status")
|
245
|
-
error_code = models.CharField(max_length=10, blank=True, help_text="Twilio error code")
|
246
|
-
error_message = models.TextField(blank=True, help_text="Error description")
|
247
|
-
|
248
|
-
# Recipient info
|
249
|
-
to_number = models.CharField(max_length=20, blank=True, help_text="Recipient phone/email")
|
250
|
-
from_number = models.CharField(max_length=20, blank=True, help_text="Sender phone/email")
|
251
|
-
|
252
|
-
# Pricing
|
253
|
-
price = models.DecimalField(max_digits=10, decimal_places=6, null=True, blank=True)
|
254
|
-
price_unit = models.CharField(max_length=3, blank=True, help_text="Currency code")
|
255
|
-
|
256
|
-
# Timestamps
|
257
|
-
created_at = models.DateTimeField(auto_now_add=True)
|
258
|
-
updated_at = models.DateTimeField(auto_now=True)
|
259
|
-
twilio_created_at = models.DateTimeField(null=True, blank=True, help_text="Timestamp from Twilio")
|
260
|
-
|
261
|
-
# Relations
|
262
|
-
otp_secret = models.ForeignKey(
|
263
|
-
'OTPSecret',
|
264
|
-
on_delete=models.SET_NULL,
|
265
|
-
null=True,
|
266
|
-
blank=True,
|
267
|
-
related_name='twilio_responses',
|
268
|
-
help_text="Related OTP if applicable"
|
269
|
-
)
|
270
|
-
|
271
|
-
class Meta:
|
272
|
-
app_label = 'django_cfg_accounts'
|
273
|
-
verbose_name = 'Twilio Response'
|
274
|
-
verbose_name_plural = 'Twilio Responses'
|
275
|
-
ordering = ['-created_at']
|
276
|
-
indexes = [
|
277
|
-
models.Index(fields=['message_sid']),
|
278
|
-
models.Index(fields=['verification_sid']),
|
279
|
-
models.Index(fields=['status', 'created_at']),
|
280
|
-
models.Index(fields=['response_type', 'service_type']),
|
281
|
-
]
|
282
|
-
|
283
|
-
def __str__(self):
|
284
|
-
identifier = self.message_sid or self.verification_sid or 'Unknown'
|
285
|
-
return f"{self.get_service_type_display()} {self.get_response_type_display()} - {identifier}"
|
286
|
-
|
287
|
-
@property
|
288
|
-
def is_successful(self):
|
289
|
-
"""Check if the response indicates success."""
|
290
|
-
success_statuses = ['sent', 'delivered', 'pending', 'approved']
|
291
|
-
return self.status.lower() in success_statuses if self.status else False
|
292
|
-
|
293
|
-
@property
|
294
|
-
def has_error(self):
|
295
|
-
"""Check if the response has an error."""
|
296
|
-
return bool(self.error_code or self.error_message)
|
297
|
-
|
298
|
-
|
299
206
|
class UserActivity(models.Model):
|
300
207
|
"""
|
301
208
|
User activity log.
|
@@ -26,7 +26,7 @@ class OTPRequestSerializer(serializers.Serializer):
|
|
26
26
|
source_url = serializers.URLField(
|
27
27
|
required=False,
|
28
28
|
allow_blank=True,
|
29
|
-
help_text="Source URL for tracking registration (e.g., https://
|
29
|
+
help_text="Source URL for tracking registration (e.g., https://unrealos.com)",
|
30
30
|
)
|
31
31
|
|
32
32
|
def validate_identifier(self, value):
|
@@ -76,7 +76,7 @@ class OTPVerifySerializer(serializers.Serializer):
|
|
76
76
|
source_url = serializers.URLField(
|
77
77
|
required=False,
|
78
78
|
allow_blank=True,
|
79
|
-
help_text="Source URL for tracking login (e.g., https://
|
79
|
+
help_text="Source URL for tracking login (e.g., https://unrealos.com)",
|
80
80
|
)
|
81
81
|
|
82
82
|
def validate_identifier(self, value):
|
{django_cfg-1.1.52 → django_cfg-1.1.53}/src/django_cfg/apps/accounts/services/otp_service.py
RENAMED
@@ -6,7 +6,7 @@ from typing import Optional, Tuple
|
|
6
6
|
import re
|
7
7
|
|
8
8
|
from django_cfg.modules.django_telegram import DjangoTelegram
|
9
|
-
from django_cfg.modules.django_twilio import SimpleTwilioService
|
9
|
+
from django_cfg.modules.django_twilio import SimpleTwilioService
|
10
10
|
from ..models import OTPSecret, CustomUser
|
11
11
|
from ..utils.notifications import AccountNotifications
|
12
12
|
from ..signals import notify_failed_otp_attempt
|
@@ -28,53 +28,6 @@ class OTPService:
|
|
28
28
|
phone_pattern = r'^\+[1-9]\d{6,14}$' # E.164 format: minimum 7 digits, maximum 15
|
29
29
|
return bool(re.match(phone_pattern, clean_phone))
|
30
30
|
|
31
|
-
@staticmethod
|
32
|
-
def _get_temp_email_domain(source_url: Optional[str] = None) -> str:
|
33
|
-
"""
|
34
|
-
Get domain for temporary email addresses.
|
35
|
-
|
36
|
-
Priority:
|
37
|
-
1. Extract domain from source_url
|
38
|
-
2. Use config.site_url domain
|
39
|
-
3. Fallback to 'temp.local'
|
40
|
-
"""
|
41
|
-
try:
|
42
|
-
from django_cfg.core.config import get_current_config
|
43
|
-
from urllib.parse import urlparse
|
44
|
-
|
45
|
-
config = get_current_config()
|
46
|
-
|
47
|
-
# Try to extract domain from source_url
|
48
|
-
if source_url:
|
49
|
-
try:
|
50
|
-
parsed = urlparse(source_url)
|
51
|
-
if parsed.netloc:
|
52
|
-
# Remove www. prefix if present
|
53
|
-
domain = parsed.netloc
|
54
|
-
if domain.startswith("www."):
|
55
|
-
domain = domain[4:]
|
56
|
-
return domain
|
57
|
-
except:
|
58
|
-
pass
|
59
|
-
|
60
|
-
# Try to use config.site_url domain
|
61
|
-
if config and config.site_url:
|
62
|
-
try:
|
63
|
-
parsed = urlparse(config.site_url)
|
64
|
-
if parsed.netloc:
|
65
|
-
domain = parsed.netloc
|
66
|
-
if domain.startswith("www."):
|
67
|
-
domain = domain[4:]
|
68
|
-
return domain
|
69
|
-
except:
|
70
|
-
pass
|
71
|
-
|
72
|
-
# Fallback to temp.local
|
73
|
-
return "temp.local"
|
74
|
-
|
75
|
-
except Exception:
|
76
|
-
return "temp.local"
|
77
|
-
|
78
31
|
@staticmethod
|
79
32
|
def _determine_channel(identifier: str) -> str:
|
80
33
|
"""Determine if identifier is email or phone."""
|
@@ -161,10 +114,8 @@ class OTPService:
|
|
161
114
|
user = CustomUser.objects.get(phone=cleaned_identifier)
|
162
115
|
created = False
|
163
116
|
except CustomUser.DoesNotExist:
|
164
|
-
# Create user with temp email based on phone
|
165
|
-
|
166
|
-
domain = OTPService._get_temp_email_domain(source_url)
|
167
|
-
temp_email = f"phone_{phone_clean}@{domain}"
|
117
|
+
# Create user with temp email based on phone
|
118
|
+
temp_email = f"phone_{cleaned_identifier.replace('+', '').replace(' ', '')}@temp.local"
|
168
119
|
user, created = CustomUser.objects.register_user(
|
169
120
|
temp_email, source_url=source_url
|
170
121
|
)
|
@@ -219,36 +170,10 @@ class OTPService:
|
|
219
170
|
user, otp_code, is_new_user=created, source_url=source_url, channel='email'
|
220
171
|
)
|
221
172
|
else: # phone channel
|
222
|
-
#
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
logger.error(f"❌ Failed to send phone OTP to {cleaned_identifier[:8]}: {message}")
|
227
|
-
return False, "phone_send_failed"
|
228
|
-
|
229
|
-
logger.info(f"🎉 Phone OTP sent successfully to {cleaned_identifier[:8]}: {message}")
|
230
|
-
|
231
|
-
# Send telegram notification for admin
|
232
|
-
try:
|
233
|
-
# Extract method from message (e.g., "OTP sent via WhatsApp... (Direct WhatsApp)")
|
234
|
-
method_used = "Hybrid WhatsApp"
|
235
|
-
if "(Direct WhatsApp)" in message:
|
236
|
-
method_used = "Direct WhatsApp"
|
237
|
-
elif "(Verify API)" in message:
|
238
|
-
method_used = "Verify API"
|
239
|
-
|
240
|
-
DjangoTelegram.send_info(
|
241
|
-
f"📱 Phone OTP Request ({method_used})",
|
242
|
-
{
|
243
|
-
"phone": cleaned_identifier,
|
244
|
-
"method": method_used,
|
245
|
-
"user_type": "New User" if created else "Existing User",
|
246
|
-
"source_url": source_url or "Direct",
|
247
|
-
"timestamp": timezone.now().strftime("%Y-%m-%d %H:%M:%S UTC")
|
248
|
-
}
|
249
|
-
)
|
250
|
-
except Exception as e:
|
251
|
-
logger.error(f"Failed to send telegram notification: {e}")
|
173
|
+
# Send OTP via SMS using Twilio
|
174
|
+
AccountNotifications.send_phone_otp_notification(
|
175
|
+
user, otp_code, cleaned_identifier, is_new_user=created, source_url=source_url
|
176
|
+
)
|
252
177
|
|
253
178
|
return True, "success"
|
254
179
|
except Exception as e:
|
@@ -306,47 +231,26 @@ class OTPService:
|
|
306
231
|
return None
|
307
232
|
|
308
233
|
try:
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
# Send notification for failed OTP attempt
|
317
|
-
AccountNotifications.send_failed_otp_attempt(
|
318
|
-
cleaned_identifier, channel=channel, reason=f"Verify API: {message}"
|
319
|
-
)
|
320
|
-
|
321
|
-
return None
|
322
|
-
|
323
|
-
logger.info(f"Twilio Verify API: OTP verified for {cleaned_identifier}")
|
324
|
-
|
325
|
-
except Exception as e:
|
326
|
-
logger.error(f"Twilio Verify API error during verification: {e}")
|
327
|
-
return None
|
328
|
-
else:
|
329
|
-
# Use OTPSecret for email channel
|
330
|
-
otp_secret = OTPSecret.objects.filter(
|
331
|
-
recipient=cleaned_identifier,
|
332
|
-
channel_type=channel,
|
333
|
-
secret=cleaned_otp,
|
334
|
-
is_used=False,
|
335
|
-
expires_at__gt=timezone.now(),
|
336
|
-
).first()
|
234
|
+
otp_secret = OTPSecret.objects.filter(
|
235
|
+
recipient=cleaned_identifier,
|
236
|
+
channel_type=channel,
|
237
|
+
secret=cleaned_otp,
|
238
|
+
is_used=False,
|
239
|
+
expires_at__gt=timezone.now(),
|
240
|
+
).first()
|
337
241
|
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
242
|
+
if not otp_secret or not otp_secret.is_valid:
|
243
|
+
logger.warning(f"Invalid OTP for {cleaned_identifier} ({channel})")
|
244
|
+
|
245
|
+
# Send notification for failed OTP attempt
|
246
|
+
AccountNotifications.send_failed_otp_attempt(
|
247
|
+
cleaned_identifier, channel=channel, reason="Invalid or expired OTP"
|
248
|
+
)
|
249
|
+
|
250
|
+
return None
|
347
251
|
|
348
|
-
|
349
|
-
|
252
|
+
# Mark OTP as used
|
253
|
+
otp_secret.mark_used()
|
350
254
|
|
351
255
|
# Get user based on channel
|
352
256
|
try:
|
@@ -0,0 +1,94 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html lang="en">
|
3
|
+
<head>
|
4
|
+
<meta charset="UTF-8">
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6
|
+
<title>Your Login Code</title>
|
7
|
+
<style>
|
8
|
+
body {
|
9
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
10
|
+
line-height: 1.6;
|
11
|
+
color: #333;
|
12
|
+
max-width: 600px;
|
13
|
+
margin: 0 auto;
|
14
|
+
padding: 20px;
|
15
|
+
background-color: #f8f9fa;
|
16
|
+
}
|
17
|
+
.container {
|
18
|
+
background: white;
|
19
|
+
border-radius: 8px;
|
20
|
+
padding: 40px;
|
21
|
+
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
22
|
+
}
|
23
|
+
.header {
|
24
|
+
text-align: center;
|
25
|
+
margin-bottom: 30px;
|
26
|
+
}
|
27
|
+
.logo {
|
28
|
+
font-size: 24px;
|
29
|
+
font-weight: bold;
|
30
|
+
color: #2563eb;
|
31
|
+
margin-bottom: 10px;
|
32
|
+
}
|
33
|
+
.otp-code {
|
34
|
+
background: #f1f5f9;
|
35
|
+
border: 2px solid #e2e8f0;
|
36
|
+
border-radius: 8px;
|
37
|
+
padding: 20px;
|
38
|
+
text-align: center;
|
39
|
+
margin: 30px 0;
|
40
|
+
}
|
41
|
+
.code {
|
42
|
+
font-size: 32px;
|
43
|
+
font-weight: bold;
|
44
|
+
color: #1e40af;
|
45
|
+
letter-spacing: 4px;
|
46
|
+
font-family: 'Courier New', monospace;
|
47
|
+
}
|
48
|
+
.warning {
|
49
|
+
background: #fef3c7;
|
50
|
+
border-left: 4px solid #f59e0b;
|
51
|
+
padding: 15px;
|
52
|
+
margin: 20px 0;
|
53
|
+
border-radius: 4px;
|
54
|
+
}
|
55
|
+
.footer {
|
56
|
+
text-align: center;
|
57
|
+
margin-top: 30px;
|
58
|
+
padding-top: 20px;
|
59
|
+
border-top: 1px solid #e5e7eb;
|
60
|
+
color: #6b7280;
|
61
|
+
font-size: 14px;
|
62
|
+
}
|
63
|
+
</style>
|
64
|
+
</head>
|
65
|
+
<body>
|
66
|
+
<div class="container">
|
67
|
+
<div class="header">
|
68
|
+
<div class="logo">🔐 {{ site_name }}</div>
|
69
|
+
<h1>Your Login Code</h1>
|
70
|
+
</div>
|
71
|
+
|
72
|
+
<p>Hello {{ user.get_full_name|default:user.username }},</p>
|
73
|
+
|
74
|
+
<p>You requested a login code for your account. Use the code below to sign in:</p>
|
75
|
+
|
76
|
+
<div class="otp-code">
|
77
|
+
<div class="code">{{ otp_code }}</div>
|
78
|
+
<p style="margin: 10px 0 0 0; color: #64748b;">Enter this code to continue</p>
|
79
|
+
</div>
|
80
|
+
|
81
|
+
<div class="warning">
|
82
|
+
<strong>⚠️ Important:</strong> This code will expire in {{ expires_minutes }} minutes.
|
83
|
+
If you didn't request this code, please ignore this email.
|
84
|
+
</div>
|
85
|
+
|
86
|
+
<p>If you're having trouble, you can request a new code or contact our support team.</p>
|
87
|
+
|
88
|
+
<div class="footer">
|
89
|
+
<p>This is an automated message from {{ site_name }}.</p>
|
90
|
+
<p>Please do not reply to this email.</p>
|
91
|
+
</div>
|
92
|
+
</div>
|
93
|
+
</body>
|
94
|
+
</html>
|
@@ -0,0 +1,16 @@
|
|
1
|
+
{{ site_name }} - Your Login Code
|
2
|
+
|
3
|
+
Hello {{ user.get_full_name|default:user.username }},
|
4
|
+
|
5
|
+
You requested a login code for your account. Use the code below to sign in:
|
6
|
+
|
7
|
+
LOGIN CODE: {{ otp_code }}
|
8
|
+
|
9
|
+
⚠️ Important: This code will expire in {{ expires_minutes }} minutes.
|
10
|
+
If you didn't request this code, please ignore this email.
|
11
|
+
|
12
|
+
If you're having trouble, you can request a new code or contact our support team.
|
13
|
+
|
14
|
+
---
|
15
|
+
This is an automated message from {{ site_name }}.
|
16
|
+
Please do not reply to this email.
|
@@ -3,7 +3,7 @@ from rest_framework.routers import DefaultRouter
|
|
3
3
|
from rest_framework_simplejwt.views import TokenRefreshView
|
4
4
|
from drf_spectacular.utils import extend_schema
|
5
5
|
|
6
|
-
from .views import OTPViewSet
|
6
|
+
from .views import OTPViewSet
|
7
7
|
from .views.profile import UserProfileView, UserProfileUpdateView, UserProfilePartialUpdateView, upload_avatar
|
8
8
|
|
9
9
|
app_name = 'cfg_accounts'
|
@@ -11,7 +11,6 @@ app_name = 'cfg_accounts'
|
|
11
11
|
# Create router for ViewSets
|
12
12
|
router = DefaultRouter()
|
13
13
|
router.register(r'otp', OTPViewSet, basename='otp')
|
14
|
-
router.register(r'webhook', TwilioWebhookViewSet, basename='webhook')
|
15
14
|
|
16
15
|
# Token-related URLs
|
17
16
|
@extend_schema(tags=['Auth'])
|