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