django-cfg 1.3.13__py3-none-any.whl → 1.4.3__py3-none-any.whl
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/__init__.py +1 -1
- django_cfg/apps/accounts/admin/user_admin.py +39 -16
- django_cfg/apps/accounts/serializers/profile.py +1 -1
- django_cfg/apps/accounts/services/otp_service.py +18 -11
- django_cfg/apps/accounts/signals.py +15 -24
- django_cfg/apps/accounts/utils/notifications.py +217 -358
- django_cfg/apps/accounts/views/otp.py +2 -2
- django_cfg/apps/accounts/views/webhook.py +1 -1
- django_cfg/apps/agents/core/django_agent.py +1 -1
- django_cfg/apps/agents/examples/__init__.py +3 -0
- django_cfg/apps/agents/examples/simple_example.py +161 -0
- django_cfg/apps/api/commands/views.py +66 -83
- django_cfg/apps/api/health/drf_views.py +269 -0
- django_cfg/apps/api/health/serializers.py +45 -0
- django_cfg/apps/api/health/urls.py +6 -1
- django_cfg/apps/knowbase/admin/actions/__init__.py +13 -0
- django_cfg/apps/knowbase/admin/actions/visibility_actions.py +56 -0
- django_cfg/apps/knowbase/admin/document_admin.py +136 -270
- django_cfg/apps/knowbase/admin/helpers/__init__.py +17 -0
- django_cfg/apps/knowbase/admin/helpers/configs.py +72 -0
- django_cfg/apps/knowbase/admin/helpers/display_helpers.py +156 -0
- django_cfg/apps/knowbase/admin/helpers/statistics.py +108 -0
- django_cfg/apps/knowbase/config/constance_fields.py +1 -1
- django_cfg/apps/knowbase/config/settings.py +2 -2
- django_cfg/apps/knowbase/examples/__init__.py +3 -0
- django_cfg/apps/knowbase/examples/external_data_usage.py +191 -0
- django_cfg/apps/knowbase/mixins/__init__.py +19 -2
- django_cfg/apps/knowbase/mixins/config/__init__.py +14 -0
- django_cfg/apps/knowbase/mixins/config/defaults.py +75 -0
- django_cfg/apps/knowbase/mixins/config/meta_config.py +120 -0
- django_cfg/apps/knowbase/mixins/creator.py +10 -10
- django_cfg/apps/knowbase/mixins/examples/vehicle_model_example.py +199 -0
- django_cfg/apps/knowbase/mixins/external_data_mixin.py +105 -403
- django_cfg/apps/knowbase/mixins/generators/__init__.py +16 -0
- django_cfg/apps/knowbase/mixins/generators/content_generator.py +218 -0
- django_cfg/apps/knowbase/mixins/generators/field_analyzer.py +76 -0
- django_cfg/apps/knowbase/mixins/generators/metadata_generator.py +124 -0
- django_cfg/apps/knowbase/mixins/service.py +2 -2
- django_cfg/apps/knowbase/services/archive/__init__.py +1 -0
- django_cfg/apps/knowbase/services/archive/analyzers/__init__.py +17 -0
- django_cfg/apps/knowbase/services/archive/analyzers/complexity_analyzer.py +33 -0
- django_cfg/apps/knowbase/services/archive/analyzers/purpose_detector.py +36 -0
- django_cfg/apps/knowbase/services/archive/analyzers/quality_analyzer.py +39 -0
- django_cfg/apps/knowbase/services/archive/analyzers/tag_generator.py +103 -0
- django_cfg/apps/knowbase/services/archive/chunking/__init__.py +19 -0
- django_cfg/apps/knowbase/services/archive/chunking/base.py +81 -0
- django_cfg/apps/knowbase/services/archive/chunking/json_chunker.py +62 -0
- django_cfg/apps/knowbase/services/archive/chunking/markdown_chunker.py +107 -0
- django_cfg/apps/knowbase/services/archive/chunking/python_chunker.py +248 -0
- django_cfg/apps/knowbase/services/archive/chunking/text_chunker.py +70 -0
- django_cfg/apps/knowbase/services/archive/chunking_service.py +110 -729
- django_cfg/apps/knowbase/services/archive/context/__init__.py +14 -0
- django_cfg/apps/knowbase/services/archive/context/builders.py +220 -0
- django_cfg/apps/knowbase/services/archive/context/models.py +38 -0
- django_cfg/apps/knowbase/services/embedding/models.py +18 -14
- django_cfg/apps/knowbase/services/embedding/processors.py +6 -3
- django_cfg/apps/knowbase/tasks/document_processing.py +11 -3
- django_cfg/apps/leads/tests.py +1 -1
- django_cfg/apps/payments/admin/api_keys_admin.py +1 -1
- django_cfg/apps/payments/admin/balance_admin.py +1 -1
- django_cfg/apps/payments/admin/currencies_admin.py +1 -1
- django_cfg/apps/payments/admin/payments_admin.py +1 -1
- django_cfg/apps/payments/admin/subscriptions_admin.py +1 -1
- django_cfg/apps/payments/admin_interface/templates/payments/base.html +59 -126
- django_cfg/apps/payments/admin_interface/views/api/payments.py +1 -1
- django_cfg/apps/payments/admin_interface/views/api/stats.py +1 -1
- django_cfg/apps/payments/admin_interface/views/api/users.py +1 -1
- django_cfg/apps/payments/admin_interface/views/api/webhook_admin.py +1 -1
- django_cfg/apps/payments/admin_interface/views/api/webhook_public.py +1 -1
- django_cfg/apps/payments/admin_interface/views/base.py +29 -2
- django_cfg/apps/payments/apps.py +1 -1
- django_cfg/apps/payments/config/django_cfg_integration.py +2 -2
- django_cfg/apps/payments/config/helpers.py +3 -2
- django_cfg/apps/payments/management/commands/cleanup_expired_data.py +1 -1
- django_cfg/apps/payments/management/commands/currency_stats.py +1 -1
- django_cfg/apps/payments/management/commands/manage_currencies.py +1 -1
- django_cfg/apps/payments/management/commands/manage_providers.py +1 -1
- django_cfg/apps/payments/management/commands/process_pending_payments.py +1 -1
- django_cfg/apps/payments/management/commands/test_providers.py +1 -1
- django_cfg/apps/payments/middleware/api_access.py +1 -1
- django_cfg/apps/payments/middleware/rate_limiting.py +1 -1
- django_cfg/apps/payments/middleware/usage_tracking.py +1 -1
- django_cfg/apps/payments/models/balance.py +2 -2
- django_cfg/apps/payments/models/managers/api_key_managers.py +1 -1
- django_cfg/apps/payments/models/managers/balance_managers.py +1 -1
- django_cfg/apps/payments/models/managers/currency_managers.py +1 -1
- django_cfg/apps/payments/models/managers/payment_managers.py +1 -1
- django_cfg/apps/payments/models/managers/subscription_managers.py +1 -1
- django_cfg/apps/payments/models/payments.py +2 -2
- django_cfg/apps/payments/services/cache_service/__init__.py +1 -1
- django_cfg/apps/payments/services/cache_service/simple_cache.py +10 -5
- django_cfg/apps/payments/services/core/base.py +1 -1
- django_cfg/apps/payments/services/core/currency/__init__.py +13 -0
- django_cfg/apps/payments/services/core/currency/currency_converter.py +57 -0
- django_cfg/apps/payments/services/core/currency/currency_validator.py +61 -0
- django_cfg/apps/payments/services/core/operations/__init__.py +15 -0
- django_cfg/apps/payments/services/core/operations/payment_canceller.py +100 -0
- django_cfg/apps/payments/services/core/operations/payment_creator.py +196 -0
- django_cfg/apps/payments/services/core/operations/status_checker.py +100 -0
- django_cfg/apps/payments/services/core/payment_service.py +124 -612
- django_cfg/apps/payments/services/core/providers/__init__.py +13 -0
- django_cfg/apps/payments/services/core/providers/provider_client.py +132 -0
- django_cfg/apps/payments/services/core/providers/status_mapper.py +89 -0
- django_cfg/apps/payments/services/core/utils/__init__.py +13 -0
- django_cfg/apps/payments/services/core/utils/data_converter.py +48 -0
- django_cfg/apps/payments/services/core/utils/statistics_calculator.py +69 -0
- django_cfg/apps/payments/services/providers/base.py +1 -1
- django_cfg/apps/payments/services/providers/nowpayments/__init__.py +3 -3
- django_cfg/apps/payments/services/providers/nowpayments/parsers/__init__.py +9 -0
- django_cfg/apps/payments/services/providers/nowpayments/parsers/data/__init__.py +23 -0
- django_cfg/apps/payments/services/providers/nowpayments/parsers/data/constants.py +23 -0
- django_cfg/apps/payments/services/providers/nowpayments/parsers/data/currency_names.py +244 -0
- django_cfg/apps/payments/services/providers/nowpayments/parsers/data/patterns.py +511 -0
- django_cfg/apps/payments/services/providers/nowpayments/parsers/parser.py +168 -0
- django_cfg/apps/payments/services/providers/nowpayments/provider.py +1 -1
- django_cfg/apps/payments/services/providers/nowpayments/sync.py +1 -1
- django_cfg/apps/payments/services/providers/registry.py +1 -1
- django_cfg/apps/payments/services/providers/sync_service.py +1 -1
- django_cfg/apps/payments/signals/__init__.py +1 -1
- django_cfg/apps/payments/signals/api_key_signals.py +1 -1
- django_cfg/apps/payments/signals/balance_signals.py +1 -1
- django_cfg/apps/payments/signals/payment_signals.py +1 -1
- django_cfg/apps/payments/signals/subscription_signals.py +1 -1
- django_cfg/apps/payments/views/api/api_keys.py +1 -1
- django_cfg/apps/payments/views/api/balances.py +1 -1
- django_cfg/apps/payments/views/api/base.py +1 -1
- django_cfg/apps/payments/views/api/currencies.py +1 -1
- django_cfg/apps/payments/views/api/payments.py +1 -1
- django_cfg/apps/payments/views/api/subscriptions.py +1 -1
- django_cfg/apps/payments/views/api/webhooks.py +1 -1
- django_cfg/apps/payments/views/serializers/api_keys.py +1 -1
- django_cfg/apps/payments/views/serializers/balances.py +1 -1
- django_cfg/apps/payments/views/serializers/currencies.py +1 -1
- django_cfg/apps/payments/views/serializers/payments.py +1 -1
- django_cfg/apps/payments/views/serializers/subscriptions.py +1 -1
- django_cfg/apps/payments/views/serializers/webhooks.py +1 -1
- django_cfg/apps/support/admin/support_admin.py +21 -13
- django_cfg/apps/support/templates/support/chat/access_denied.html +21 -27
- django_cfg/apps/support/templates/support/chat/ticket_chat.html +183 -254
- django_cfg/apps/support/utils/support_email_service.py +1 -1
- django_cfg/apps/tasks/templates/tasks/layout/base.html +20 -115
- django_cfg/apps/tasks/utils/simulator.py +1 -1
- django_cfg/apps/tasks/views/dashboard.py +33 -3
- django_cfg/apps/urls.py +5 -1
- django_cfg/cli/README.md +57 -471
- django_cfg/cli/commands/create_project.py +140 -529
- django_cfg/cli/main.py +13 -10
- django_cfg/core/__init__.py +63 -6
- django_cfg/core/base/__init__.py +5 -0
- django_cfg/core/base/config_model.py +652 -0
- django_cfg/core/builders/__init__.py +11 -0
- django_cfg/core/builders/apps_builder.py +258 -0
- django_cfg/core/builders/middleware_builder.py +115 -0
- django_cfg/core/builders/security_builder.py +96 -0
- django_cfg/core/config.py +20 -892
- django_cfg/core/constants.py +69 -0
- django_cfg/core/environment/__init__.py +9 -0
- django_cfg/core/exceptions.py +45 -298
- django_cfg/core/generation/__init__.py +51 -0
- django_cfg/core/generation/core_generators/__init__.py +0 -0
- django_cfg/core/generation/core_generators/settings.py +90 -0
- django_cfg/core/generation/core_generators/static.py +82 -0
- django_cfg/core/generation/core_generators/templates.py +141 -0
- django_cfg/core/generation/data_generators/__init__.py +15 -0
- django_cfg/core/generation/data_generators/cache.py +132 -0
- django_cfg/core/generation/data_generators/database.py +117 -0
- django_cfg/core/generation/generation.py +92 -0
- django_cfg/core/generation/integration_generators/__init__.py +21 -0
- django_cfg/core/generation/integration_generators/api.py +237 -0
- django_cfg/core/generation/integration_generators/sessions.py +65 -0
- django_cfg/core/generation/integration_generators/tailwind.py +54 -0
- django_cfg/core/generation/integration_generators/tasks.py +92 -0
- django_cfg/core/generation/integration_generators/third_party.py +144 -0
- django_cfg/core/generation/orchestrator.py +285 -0
- django_cfg/core/generation/protocols.py +30 -0
- django_cfg/core/generation/security_generators/__init__.py +0 -0
- django_cfg/core/generation/utility_generators/__init__.py +24 -0
- django_cfg/core/generation/utility_generators/email.py +58 -0
- django_cfg/core/generation/utility_generators/i18n.py +66 -0
- django_cfg/core/generation/utility_generators/limits.py +58 -0
- django_cfg/core/generation/utility_generators/logging.py +66 -0
- django_cfg/core/generation/utility_generators/security.py +101 -0
- django_cfg/core/generation/utils/__init__.py +0 -0
- django_cfg/core/generation/utils/helpers.py +32 -0
- django_cfg/core/integration/__init__.py +18 -25
- django_cfg/core/integration/display/startup.py +146 -133
- django_cfg/core/integration/url_integration.py +13 -2
- django_cfg/core/services/__init__.py +5 -0
- django_cfg/core/services/config_service.py +121 -0
- django_cfg/core/state/__init__.py +9 -0
- django_cfg/core/state/registry.py +84 -0
- django_cfg/core/types/__init__.py +15 -0
- django_cfg/core/types/aliases.py +15 -0
- django_cfg/core/types/enums.py +49 -0
- django_cfg/dashboard/DEBUG_README.md +105 -0
- django_cfg/dashboard/REFACTORING_SUMMARY.md +237 -0
- django_cfg/dashboard/__init__.py +24 -0
- django_cfg/dashboard/components.py +308 -0
- django_cfg/dashboard/debug.py +176 -0
- django_cfg/dashboard/management/__init__.py +0 -0
- django_cfg/dashboard/management/commands/__init__.py +0 -0
- django_cfg/dashboard/management/commands/debug_dashboard.py +109 -0
- django_cfg/dashboard/sections/__init__.py +1 -0
- django_cfg/dashboard/sections/base.py +128 -0
- django_cfg/dashboard/sections/commands.py +32 -0
- django_cfg/dashboard/sections/overview.py +394 -0
- django_cfg/dashboard/sections/stats.py +48 -0
- django_cfg/dashboard/sections/system.py +73 -0
- django_cfg/management/commands/check_settings.py +6 -2
- django_cfg/management/commands/clear_constance.py +6 -1
- django_cfg/management/commands/create_token.py +5 -4
- django_cfg/management/commands/generate.py +5 -0
- django_cfg/management/commands/list_urls.py +7 -2
- django_cfg/management/commands/migrate_all.py +6 -2
- django_cfg/management/commands/migrator.py +6 -1
- django_cfg/management/commands/rundramatiq.py +6 -1
- django_cfg/management/commands/rundramatiq_simulator.py +11 -4
- django_cfg/management/commands/runserver_ngrok.py +9 -7
- django_cfg/management/commands/script.py +25 -21
- django_cfg/management/commands/show_config.py +6 -1
- django_cfg/management/commands/show_urls.py +8 -3
- django_cfg/management/commands/superuser.py +5 -4
- django_cfg/management/commands/task_clear.py +8 -3
- django_cfg/management/commands/task_status.py +8 -3
- django_cfg/management/commands/test_email.py +6 -1
- django_cfg/management/commands/test_telegram.py +6 -1
- django_cfg/management/commands/test_twilio.py +6 -1
- django_cfg/management/commands/tree.py +7 -4
- django_cfg/models/__init__.py +88 -3
- django_cfg/models/api/__init__.py +27 -0
- django_cfg/models/{api.py → api/config.py} +1 -1
- django_cfg/models/api/drf/__init__.py +21 -0
- django_cfg/models/api/drf/config.py +101 -0
- django_cfg/models/api/drf/redoc.py +31 -0
- django_cfg/models/api/drf/spectacular.py +129 -0
- django_cfg/models/api/drf/swagger.py +59 -0
- django_cfg/models/{api_keys.py → api/keys.py} +16 -6
- django_cfg/models/{limits.py → api/limits.py} +0 -1
- django_cfg/models/base/__init__.py +14 -0
- django_cfg/models/django/__init__.py +16 -0
- django_cfg/models/{constance.py → django/constance.py} +1 -1
- django_cfg/models/{environment.py → django/environment.py} +1 -1
- django_cfg/models/infrastructure/__init__.py +17 -0
- django_cfg/models/{cache.py → infrastructure/cache.py} +3 -2
- django_cfg/models/infrastructure/database/__init__.py +22 -0
- django_cfg/models/infrastructure/database/config.py +265 -0
- django_cfg/models/infrastructure/database/converters.py +91 -0
- django_cfg/models/infrastructure/database/parsers.py +96 -0
- django_cfg/models/infrastructure/database/routing.py +85 -0
- django_cfg/models/infrastructure/database/validators.py +170 -0
- django_cfg/models/{logging.py → infrastructure/logging.py} +1 -1
- django_cfg/models/{security.py → infrastructure/security.py} +2 -2
- django_cfg/models/ngrok/__init__.py +11 -0
- django_cfg/models/ngrok/auth.py +37 -0
- django_cfg/models/ngrok/config.py +77 -0
- django_cfg/models/ngrok/tunnel.py +35 -0
- django_cfg/models/payments/__init__.py +20 -0
- django_cfg/models/payments/api_keys.py +57 -0
- django_cfg/models/{payments.py → payments/config.py} +56 -154
- django_cfg/models/payments/providers/__init__.py +15 -0
- django_cfg/models/payments/providers/base.py +25 -0
- django_cfg/models/payments/providers/nowpayments.py +48 -0
- django_cfg/models/services/__init__.py +18 -0
- django_cfg/models/services/base.py +65 -0
- django_cfg/models/{email.py → services/email.py} +1 -1
- django_cfg/models/services/telegram.py +172 -0
- django_cfg/models/tasks/__init__.py +51 -0
- django_cfg/models/tasks/backends.py +250 -0
- django_cfg/models/tasks/config.py +314 -0
- django_cfg/models/tasks/utils.py +174 -0
- django_cfg/modules/base.py +18 -3
- django_cfg/modules/django_admin/decorators/actions.py +1 -1
- django_cfg/modules/django_admin/decorators/display.py +1 -1
- django_cfg/modules/django_admin/mixins/standalone_actions_mixin.py +1 -1
- django_cfg/modules/django_currency/examples/__init__.py +3 -0
- django_cfg/modules/django_currency/examples/example_database_usage.py +144 -0
- django_cfg/modules/django_drf_theme/CHANGELOG.md +210 -0
- django_cfg/modules/django_drf_theme/EXAMPLE.md +465 -0
- django_cfg/modules/django_drf_theme/IMPLEMENTATION.md +232 -0
- django_cfg/modules/django_drf_theme/README.md +207 -0
- django_cfg/modules/django_drf_theme/TAILWIND_CDN_GUIDE.md +274 -0
- django_cfg/modules/django_drf_theme/__init__.py +23 -0
- django_cfg/modules/django_drf_theme/apps.py +15 -0
- django_cfg/modules/django_drf_theme/renderers.py +58 -0
- django_cfg/modules/django_drf_theme/templates/rest_framework/tailwind/api.html +375 -0
- django_cfg/modules/django_drf_theme/templates/rest_framework/tailwind/base.html +938 -0
- django_cfg/modules/django_drf_theme/templates/rest_framework/tailwind/forms/filter_form.html +132 -0
- django_cfg/modules/django_drf_theme/templates/rest_framework/tailwind/forms/raw_data_form.html +123 -0
- django_cfg/modules/django_drf_theme/templatetags/__init__.py +1 -0
- django_cfg/modules/django_drf_theme/templatetags/tailwind_tags.py +57 -0
- django_cfg/modules/django_email/__init__.py +14 -0
- django_cfg/modules/{django_email.py → django_email/service.py} +78 -113
- django_cfg/modules/django_email/utils.py +40 -0
- django_cfg/modules/django_health/__init__.py +9 -0
- django_cfg/modules/{django_health.py → django_health/service.py} +23 -21
- django_cfg/modules/django_ipc_client/README.md +346 -0
- django_cfg/modules/django_ipc_client/__init__.py +51 -0
- django_cfg/modules/django_ipc_client/client.py +540 -0
- django_cfg/modules/django_ipc_client/config.py +207 -0
- django_cfg/modules/django_ipc_client/dashboard/README.md +517 -0
- django_cfg/modules/django_ipc_client/dashboard/UNFOLD_INTEGRATION.md +439 -0
- django_cfg/modules/django_ipc_client/dashboard/__init__.py +11 -0
- django_cfg/modules/django_ipc_client/dashboard/apps.py +22 -0
- django_cfg/modules/django_ipc_client/dashboard/monitor.py +435 -0
- django_cfg/modules/django_ipc_client/dashboard/static/django_ipc_dashboard/js/dashboard.js +373 -0
- django_cfg/modules/django_ipc_client/dashboard/templates/django_ipc_dashboard/base.html +76 -0
- django_cfg/modules/django_ipc_client/dashboard/templates/django_ipc_dashboard/dashboard.html +200 -0
- django_cfg/modules/django_ipc_client/dashboard/urls.py +22 -0
- django_cfg/modules/django_ipc_client/dashboard/urls_admin.py +9 -0
- django_cfg/modules/django_ipc_client/dashboard/views.py +251 -0
- django_cfg/modules/django_ipc_client/exceptions.py +201 -0
- django_cfg/modules/django_llm/llm/client.py +155 -550
- django_cfg/modules/django_llm/llm/embeddings/__init__.py +13 -0
- django_cfg/modules/django_llm/llm/embeddings/mock_embedder.py +106 -0
- django_cfg/modules/django_llm/llm/embeddings/openai_embedder.py +79 -0
- django_cfg/modules/django_llm/llm/models_api/__init__.py +9 -0
- django_cfg/modules/django_llm/llm/models_api/models_query.py +163 -0
- django_cfg/modules/django_llm/llm/providers/__init__.py +15 -0
- django_cfg/modules/django_llm/llm/providers/config_builder.py +103 -0
- django_cfg/modules/django_llm/llm/providers/provider_manager.py +148 -0
- django_cfg/modules/django_llm/llm/providers/provider_selector.py +60 -0
- django_cfg/modules/django_llm/llm/requests/__init__.py +15 -0
- django_cfg/modules/django_llm/llm/requests/cache_manager.py +170 -0
- django_cfg/modules/django_llm/llm/requests/chat_handler.py +199 -0
- django_cfg/modules/django_llm/llm/requests/embedding_handler.py +113 -0
- django_cfg/modules/django_llm/llm/responses/__init__.py +9 -0
- django_cfg/modules/django_llm/llm/responses/response_builder.py +131 -0
- django_cfg/modules/django_llm/llm/stats/__init__.py +9 -0
- django_cfg/modules/django_llm/llm/stats/stats_manager.py +107 -0
- django_cfg/modules/django_llm/translator/detectors/__init__.py +13 -0
- django_cfg/modules/django_llm/translator/detectors/language_detector.py +90 -0
- django_cfg/modules/django_llm/translator/detectors/script_detector.py +153 -0
- django_cfg/modules/django_llm/translator/stats/__init__.py +11 -0
- django_cfg/modules/django_llm/translator/stats/stats_tracker.py +85 -0
- django_cfg/modules/django_llm/translator/translator.py +150 -603
- django_cfg/modules/django_llm/translator/translators/__init__.py +15 -0
- django_cfg/modules/django_llm/translator/translators/json_translator.py +316 -0
- django_cfg/modules/django_llm/translator/translators/text_translator.py +139 -0
- django_cfg/modules/django_llm/translator/utils/__init__.py +13 -0
- django_cfg/modules/django_llm/translator/utils/prompt_builder.py +110 -0
- django_cfg/modules/django_llm/translator/utils/text_utils.py +114 -0
- django_cfg/modules/django_logging/FIXES_SUMMARY.md +276 -0
- django_cfg/modules/django_logging/LOGGING_GUIDE.md +504 -0
- django_cfg/modules/django_logging/__init__.py +14 -0
- django_cfg/modules/{django_logger.py → django_logging/django_logger.py} +13 -13
- django_cfg/modules/{logger.py → django_logging/logger.py} +14 -4
- django_cfg/modules/django_ngrok/__init__.py +39 -0
- django_cfg/modules/{django_ngrok.py → django_ngrok/service.py} +14 -42
- django_cfg/modules/django_rpc_old/POETRY.md +344 -0
- django_cfg/modules/django_rpc_old/README.md +397 -0
- django_cfg/modules/django_rpc_old/TESTING.md +358 -0
- django_cfg/modules/django_rpc_old/__init__.py +39 -0
- django_cfg/modules/django_rpc_old/client.py +531 -0
- django_cfg/modules/django_rpc_old/config.py +279 -0
- django_cfg/modules/django_rpc_old/exceptions.py +172 -0
- django_cfg/modules/django_tailwind/README.md +478 -0
- django_cfg/modules/django_tailwind/__init__.py +7 -0
- django_cfg/modules/django_tailwind/apps.py +10 -0
- django_cfg/modules/django_tailwind/templates/django_tailwind/app.html +5 -0
- django_cfg/modules/django_tailwind/templates/django_tailwind/base.html +117 -0
- django_cfg/modules/django_tailwind/templates/django_tailwind/components/navbar.html +124 -0
- django_cfg/modules/django_tailwind/templates/django_tailwind/components/theme_toggle.html +54 -0
- django_cfg/modules/django_tailwind/templates/django_tailwind/components/user_menu.html +116 -0
- django_cfg/modules/django_tailwind/templates/django_tailwind/simple.html +46 -0
- django_cfg/modules/django_tailwind/templatetags/__init__.py +1 -0
- django_cfg/modules/django_tailwind/templatetags/tailwind_info.py +185 -0
- django_cfg/modules/django_tasks/__init__.py +29 -0
- django_cfg/modules/django_tasks/factory.py +127 -0
- django_cfg/modules/{django_tasks.py → django_tasks/service.py} +45 -274
- django_cfg/modules/django_tasks/settings.py +107 -0
- django_cfg/modules/django_telegram/__init__.py +29 -0
- django_cfg/modules/{django_telegram.py → django_telegram/service.py} +45 -113
- django_cfg/modules/django_telegram/utils.py +62 -0
- django_cfg/modules/django_twilio/__init__.py +54 -107
- django_cfg/modules/django_twilio/_imports.py +30 -0
- django_cfg/modules/django_twilio/base.py +192 -0
- django_cfg/modules/django_twilio/email_otp.py +227 -0
- django_cfg/modules/django_twilio/sendgrid_service.py +1 -1
- django_cfg/modules/django_twilio/simple_service.py +1 -2
- django_cfg/modules/django_twilio/sms.py +94 -0
- django_cfg/modules/django_twilio/twilio_service.py +2 -3
- django_cfg/modules/django_twilio/unified.py +310 -0
- django_cfg/modules/django_twilio/utils.py +190 -0
- django_cfg/modules/django_twilio/whatsapp.py +137 -0
- django_cfg/modules/django_unfold/callbacks/base.py +198 -7
- django_cfg/modules/django_unfold/callbacks/main.py +102 -10
- django_cfg/modules/django_unfold/dashboard.py +65 -43
- django_cfg/modules/django_unfold/models/config.py +13 -12
- django_cfg/modules/django_unfold/models/navigation.py +8 -3
- django_cfg/modules/django_unfold/models/tabs.py +2 -2
- django_cfg/modules/django_unfold/templates/unfold/helpers/app_list.html +102 -0
- django_cfg/registry/core.py +24 -26
- django_cfg/registry/modules.py +5 -2
- django_cfg/registry/services.py +20 -3
- django_cfg/registry/third_party.py +8 -8
- django_cfg/static/admin/css/dashboard.css +260 -0
- django_cfg/static/admin/js/commands.js +171 -0
- django_cfg/static/admin/js/dashboard.js +126 -0
- django_cfg/templates/admin/components/management_commands.js +375 -0
- django_cfg/templates/admin/components/progress_bar.html +18 -23
- django_cfg/templates/admin/examples/component_class_example.html +156 -0
- django_cfg/templates/admin/index.html +48 -20
- django_cfg/templates/admin/index_new.html +106 -0
- django_cfg/templates/admin/layouts/base_dashboard.html +60 -0
- django_cfg/templates/admin/layouts/dashboard_with_tabs.html +1 -20
- django_cfg/templates/admin/sections/commands_section.html +626 -0
- django_cfg/templates/admin/sections/overview_section.html +112 -0
- django_cfg/templates/admin/sections/stats_section.html +35 -0
- django_cfg/templates/admin/sections/system_section.html +99 -0
- django_cfg/templates/admin/snippets/components/CHARTS_GUIDE.md +322 -0
- django_cfg/templates/admin/snippets/components/activity_tracker.html +85 -47
- django_cfg/templates/admin/snippets/components/charts_section.html +154 -64
- django_cfg/templates/admin/snippets/components/django_commands.html +3 -3
- django_cfg/templates/admin/snippets/components/recent_activity_improved.html +25 -0
- django_cfg/templates/admin/snippets/components/recent_users_table.html +1 -1
- django_cfg/templates/admin/snippets/components/system_metrics.html +179 -93
- django_cfg/templates/admin/snippets/zones/zones_table.html +2 -2
- django_cfg/templatetags/django_cfg.py +7 -1
- django_cfg/utils/smart_defaults.py +4 -4
- django_cfg-1.4.3.dist-info/METADATA +533 -0
- {django_cfg-1.3.13.dist-info → django_cfg-1.4.3.dist-info}/RECORD +432 -195
- django_cfg/apps/accounts/utils/auth_email_service.py +0 -84
- django_cfg/apps/payments/services/providers/nowpayments/parsers.py +0 -879
- django_cfg/core/generation.py +0 -621
- django_cfg/management/commands/validate_config.py +0 -189
- django_cfg/models/database.py +0 -480
- django_cfg/models/drf.py +0 -272
- django_cfg/models/ngrok.py +0 -122
- django_cfg/models/services.py +0 -440
- django_cfg/models/tasks.py +0 -550
- django_cfg/modules/django_twilio/service.py +0 -942
- django_cfg/template_archive/django_sample.zip +0 -0
- django_cfg/templates/rest_framework/api.html +0 -12
- django_cfg/utils/toolkit.py +0 -703
- django_cfg-1.3.13.dist-info/METADATA +0 -1029
- /django_cfg/apps/accounts/management/commands/{test_otp.py → otp_test.py} +0 -0
- /django_cfg/core/{environment.py → environment/detector.py} +0 -0
- /django_cfg/models/{cors.py → api/cors.py} +0 -0
- /django_cfg/models/{jwt.py → api/jwt.py} +0 -0
- /django_cfg/models/{base.py → base/config.py} +0 -0
- /django_cfg/models/{cfg.py → base/module.py} +0 -0
- /django_cfg/models/{revolution.py → django/revolution.py} +0 -0
- /django_cfg/modules/{dramatiq_setup.py → django_tasks/dramatiq_setup.py} +0 -0
- {django_cfg-1.3.13.dist-info → django_cfg-1.4.3.dist-info}/WHEEL +0 -0
- {django_cfg-1.3.13.dist-info → django_cfg-1.4.3.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.3.13.dist-info → django_cfg-1.4.3.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,30 @@
|
|
1
|
+
"""
|
2
|
+
Optional Twilio imports.
|
3
|
+
|
4
|
+
Makes twilio and sendgrid imports optional for testing and when not using Twilio functionality.
|
5
|
+
"""
|
6
|
+
|
7
|
+
# Third-party imports (optional - only required when using Twilio functionality)
|
8
|
+
try:
|
9
|
+
from twilio.rest import Client
|
10
|
+
from twilio.base.exceptions import TwilioException
|
11
|
+
TWILIO_AVAILABLE = True
|
12
|
+
except ImportError:
|
13
|
+
Client = None # type: ignore
|
14
|
+
TwilioException = Exception # Fallback to base exception
|
15
|
+
TWILIO_AVAILABLE = False
|
16
|
+
|
17
|
+
try:
|
18
|
+
from sendgrid import SendGridAPIClient
|
19
|
+
SENDGRID_AVAILABLE = True
|
20
|
+
except ImportError:
|
21
|
+
SendGridAPIClient = None # type: ignore
|
22
|
+
SENDGRID_AVAILABLE = False
|
23
|
+
|
24
|
+
__all__ = [
|
25
|
+
'Client',
|
26
|
+
'TwilioException',
|
27
|
+
'SendGridAPIClient',
|
28
|
+
'TWILIO_AVAILABLE',
|
29
|
+
'SENDGRID_AVAILABLE',
|
30
|
+
]
|
@@ -0,0 +1,192 @@
|
|
1
|
+
"""
|
2
|
+
Base service class for all Twilio operations.
|
3
|
+
|
4
|
+
Provides auto-configuration from DjangoConfig and common utilities
|
5
|
+
for all Twilio services including error handling and logging.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import asyncio
|
9
|
+
import logging
|
10
|
+
import random
|
11
|
+
import string
|
12
|
+
from typing import Optional, Dict, Any
|
13
|
+
from datetime import datetime, timedelta
|
14
|
+
|
15
|
+
# Third-party imports (optional)
|
16
|
+
from ._imports import Client, TwilioException, SendGridAPIClient, TWILIO_AVAILABLE, SENDGRID_AVAILABLE
|
17
|
+
|
18
|
+
# Django CFG imports
|
19
|
+
from django_cfg.modules.base import BaseCfgModule
|
20
|
+
from django_cfg.modules.django_twilio.models import TwilioConfig
|
21
|
+
from django_cfg.modules.django_twilio.exceptions import (
|
22
|
+
TwilioConfigurationError,
|
23
|
+
)
|
24
|
+
|
25
|
+
logger = logging.getLogger(__name__)
|
26
|
+
|
27
|
+
|
28
|
+
def is_async_context() -> bool:
|
29
|
+
"""Detect if running in async context."""
|
30
|
+
try:
|
31
|
+
asyncio.get_running_loop()
|
32
|
+
return True
|
33
|
+
except RuntimeError:
|
34
|
+
return False
|
35
|
+
|
36
|
+
|
37
|
+
class BaseTwilioService(BaseCfgModule):
|
38
|
+
"""
|
39
|
+
Base service class for all Twilio operations.
|
40
|
+
|
41
|
+
Provides auto-configuration from DjangoConfig and common utilities
|
42
|
+
for all Twilio services including error handling and logging.
|
43
|
+
"""
|
44
|
+
|
45
|
+
def __init__(self):
|
46
|
+
"""Initialize with auto-discovered configuration."""
|
47
|
+
super().__init__()
|
48
|
+
self._config: Optional[TwilioConfig] = None
|
49
|
+
self._twilio_client: Optional[Client] = None
|
50
|
+
self._sendgrid_client: Optional[SendGridAPIClient] = None
|
51
|
+
self._otp_storage: Dict[str, Dict[str, Any]] = {} # In-memory storage for development
|
52
|
+
|
53
|
+
def get_twilio_config(self) -> TwilioConfig:
|
54
|
+
"""
|
55
|
+
Get Twilio configuration from DjangoConfig.
|
56
|
+
|
57
|
+
Returns:
|
58
|
+
TwilioConfig instance
|
59
|
+
|
60
|
+
Raises:
|
61
|
+
TwilioConfigurationError: If configuration is missing or invalid
|
62
|
+
"""
|
63
|
+
if self._config is None:
|
64
|
+
django_config = self.get_config()
|
65
|
+
if not django_config:
|
66
|
+
raise TwilioConfigurationError(
|
67
|
+
"DjangoConfig instance not found",
|
68
|
+
suggestions=["Ensure DjangoConfig is properly initialized"]
|
69
|
+
)
|
70
|
+
|
71
|
+
twilio_config = getattr(django_config, 'twilio', None)
|
72
|
+
if not twilio_config:
|
73
|
+
raise TwilioConfigurationError(
|
74
|
+
"Twilio configuration not found in DjangoConfig",
|
75
|
+
missing_fields=["twilio"],
|
76
|
+
suggestions=["Add TwilioConfig to your DjangoConfig class"]
|
77
|
+
)
|
78
|
+
|
79
|
+
self._config = twilio_config
|
80
|
+
|
81
|
+
return self._config
|
82
|
+
|
83
|
+
def get_twilio_client(self) -> Client:
|
84
|
+
"""
|
85
|
+
Get initialized Twilio client.
|
86
|
+
|
87
|
+
Returns:
|
88
|
+
Twilio Client instance
|
89
|
+
|
90
|
+
Raises:
|
91
|
+
TwilioConfigurationError: If client cannot be initialized
|
92
|
+
"""
|
93
|
+
if self._twilio_client is None:
|
94
|
+
config = self.get_twilio_config()
|
95
|
+
|
96
|
+
try:
|
97
|
+
client_config = config.get_client_config()
|
98
|
+
self._twilio_client = Client(
|
99
|
+
client_config["username"],
|
100
|
+
client_config["password"],
|
101
|
+
region=client_config.get("region")
|
102
|
+
)
|
103
|
+
|
104
|
+
# Test connection with a simple API call
|
105
|
+
try:
|
106
|
+
self._twilio_client.api.v2010.accounts(config.account_sid).fetch()
|
107
|
+
except TwilioException as e:
|
108
|
+
raise TwilioConfigurationError(
|
109
|
+
f"Failed to authenticate with Twilio: {e}",
|
110
|
+
error_code=getattr(e, 'code', None),
|
111
|
+
suggestions=[
|
112
|
+
"Verify TWILIO_ACCOUNT_SID is correct",
|
113
|
+
"Verify TWILIO_AUTH_TOKEN is correct",
|
114
|
+
"Check Twilio account status"
|
115
|
+
]
|
116
|
+
) from e
|
117
|
+
|
118
|
+
except Exception as e:
|
119
|
+
raise TwilioConfigurationError(
|
120
|
+
f"Failed to initialize Twilio client: {e}",
|
121
|
+
suggestions=["Check Twilio configuration parameters"]
|
122
|
+
) from e
|
123
|
+
|
124
|
+
return self._twilio_client
|
125
|
+
|
126
|
+
def get_sendgrid_client(self) -> Optional[SendGridAPIClient]:
|
127
|
+
"""
|
128
|
+
Get initialized SendGrid client.
|
129
|
+
|
130
|
+
Returns:
|
131
|
+
SendGrid client instance or None if not configured
|
132
|
+
|
133
|
+
Raises:
|
134
|
+
TwilioConfigurationError: If client cannot be initialized
|
135
|
+
"""
|
136
|
+
config = self.get_twilio_config()
|
137
|
+
|
138
|
+
if not config.sendgrid:
|
139
|
+
return None
|
140
|
+
|
141
|
+
if self._sendgrid_client is None:
|
142
|
+
try:
|
143
|
+
sendgrid_config = config.get_sendgrid_config()
|
144
|
+
if sendgrid_config:
|
145
|
+
self._sendgrid_client = SendGridAPIClient(
|
146
|
+
api_key=sendgrid_config["api_key"]
|
147
|
+
)
|
148
|
+
|
149
|
+
except Exception as e:
|
150
|
+
raise TwilioConfigurationError(
|
151
|
+
f"Failed to initialize SendGrid client: {e}",
|
152
|
+
suggestions=["Check SendGrid API key configuration"]
|
153
|
+
) from e
|
154
|
+
|
155
|
+
return self._sendgrid_client
|
156
|
+
|
157
|
+
def _generate_otp(self, length: int = 6) -> str:
|
158
|
+
"""Generate numeric OTP code."""
|
159
|
+
return ''.join(random.choices(string.digits, k=length))
|
160
|
+
|
161
|
+
def _store_otp(self, identifier: str, code: str, ttl_seconds: int = 600) -> None:
|
162
|
+
"""Store OTP code with expiration (in-memory for development)."""
|
163
|
+
self._otp_storage[identifier] = {
|
164
|
+
'code': code,
|
165
|
+
'created_at': datetime.now(),
|
166
|
+
'expires_at': datetime.now() + timedelta(seconds=ttl_seconds),
|
167
|
+
'attempts': 0,
|
168
|
+
}
|
169
|
+
|
170
|
+
def _get_stored_otp(self, identifier: str) -> Optional[Dict[str, Any]]:
|
171
|
+
"""Get stored OTP data."""
|
172
|
+
return self._otp_storage.get(identifier)
|
173
|
+
|
174
|
+
def _remove_otp(self, identifier: str) -> None:
|
175
|
+
"""Remove OTP from storage."""
|
176
|
+
self._otp_storage.pop(identifier, None)
|
177
|
+
|
178
|
+
def _mask_identifier(self, identifier: str) -> str:
|
179
|
+
"""Mask identifier for security in logs."""
|
180
|
+
if "@" in identifier: # Email
|
181
|
+
parts = identifier.split("@")
|
182
|
+
if len(parts) == 2:
|
183
|
+
return f"{parts[0][:2]}***@{parts[1]}"
|
184
|
+
else: # Phone number
|
185
|
+
return f"***{identifier[-4:]}" if len(identifier) > 4 else "***"
|
186
|
+
return "***"
|
187
|
+
|
188
|
+
|
189
|
+
__all__ = [
|
190
|
+
"is_async_context",
|
191
|
+
"BaseTwilioService",
|
192
|
+
]
|
@@ -0,0 +1,227 @@
|
|
1
|
+
"""
|
2
|
+
Email OTP service using SendGrid.
|
3
|
+
|
4
|
+
Provides OTP delivery via email with template support and
|
5
|
+
comprehensive deliverability optimization.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import logging
|
9
|
+
from typing import Optional, Tuple, Dict, Any
|
10
|
+
from django_cfg.modules.django_twilio._imports import SendGridAPIClient, SENDGRID_AVAILABLE
|
11
|
+
|
12
|
+
# Import Mail helper conditionally
|
13
|
+
try:
|
14
|
+
from sendgrid.helpers.mail import Mail
|
15
|
+
except ImportError:
|
16
|
+
Mail = None # type: ignore
|
17
|
+
|
18
|
+
from asgiref.sync import sync_to_async
|
19
|
+
|
20
|
+
from .base import BaseTwilioService
|
21
|
+
from .models import SendGridConfig
|
22
|
+
from .exceptions import (
|
23
|
+
TwilioConfigurationError,
|
24
|
+
TwilioSendError,
|
25
|
+
)
|
26
|
+
|
27
|
+
logger = logging.getLogger(__name__)
|
28
|
+
|
29
|
+
|
30
|
+
class EmailOTPService(BaseTwilioService):
|
31
|
+
"""
|
32
|
+
Email OTP service using SendGrid.
|
33
|
+
|
34
|
+
Provides OTP delivery via email with template support and
|
35
|
+
comprehensive deliverability optimization.
|
36
|
+
"""
|
37
|
+
|
38
|
+
def send_otp(
|
39
|
+
self,
|
40
|
+
email: str,
|
41
|
+
subject: Optional[str] = None,
|
42
|
+
template_data: Optional[Dict[str, Any]] = None
|
43
|
+
) -> Tuple[bool, str, str]:
|
44
|
+
"""
|
45
|
+
Send OTP via email.
|
46
|
+
|
47
|
+
Args:
|
48
|
+
email: Recipient email address
|
49
|
+
subject: Custom email subject (uses default if not provided)
|
50
|
+
template_data: Additional data for email template
|
51
|
+
|
52
|
+
Returns:
|
53
|
+
Tuple[bool, str, str]: (success, message, otp_code)
|
54
|
+
|
55
|
+
Raises:
|
56
|
+
TwilioConfigurationError: If SendGrid is not configured
|
57
|
+
TwilioSendError: If email sending fails
|
58
|
+
"""
|
59
|
+
config = self.get_twilio_config()
|
60
|
+
|
61
|
+
if not config.sendgrid:
|
62
|
+
raise TwilioConfigurationError(
|
63
|
+
"SendGrid configuration not found",
|
64
|
+
missing_fields=["sendgrid"],
|
65
|
+
suggestions=["Configure SendGridConfig in your Twilio settings"]
|
66
|
+
)
|
67
|
+
|
68
|
+
sendgrid_client = self.get_sendgrid_client()
|
69
|
+
if not sendgrid_client:
|
70
|
+
raise TwilioConfigurationError("SendGrid client not initialized")
|
71
|
+
|
72
|
+
try:
|
73
|
+
# Generate OTP code
|
74
|
+
otp_code = self._generate_otp(6)
|
75
|
+
|
76
|
+
# Store OTP for verification
|
77
|
+
self._store_otp(email, otp_code, config.verify.ttl_seconds if config.verify else 600)
|
78
|
+
|
79
|
+
# Prepare email content
|
80
|
+
if config.sendgrid.otp_template_id:
|
81
|
+
# Use dynamic template
|
82
|
+
success, message = self._send_template_email(
|
83
|
+
sendgrid_client, config.sendgrid, email, otp_code, template_data
|
84
|
+
)
|
85
|
+
else:
|
86
|
+
# Use simple HTML email
|
87
|
+
success, message = self._send_simple_email(
|
88
|
+
sendgrid_client, config.sendgrid, email, otp_code, subject
|
89
|
+
)
|
90
|
+
|
91
|
+
if success:
|
92
|
+
logger.info(f"Email OTP sent successfully to {self._mask_identifier(email)}")
|
93
|
+
return True, message, otp_code
|
94
|
+
else:
|
95
|
+
raise TwilioSendError(message, channel="email", recipient=email)
|
96
|
+
|
97
|
+
except Exception as e:
|
98
|
+
if isinstance(e, TwilioSendError):
|
99
|
+
raise
|
100
|
+
raise TwilioSendError(
|
101
|
+
f"Failed to send email OTP: {e}",
|
102
|
+
channel="email",
|
103
|
+
recipient=email
|
104
|
+
) from e
|
105
|
+
|
106
|
+
async def asend_otp(
|
107
|
+
self,
|
108
|
+
email: str,
|
109
|
+
subject: Optional[str] = None,
|
110
|
+
template_data: Optional[Dict[str, Any]] = None
|
111
|
+
) -> Tuple[bool, str, str]:
|
112
|
+
"""Async version of send_otp."""
|
113
|
+
return await sync_to_async(self.send_otp)(email, subject, template_data)
|
114
|
+
|
115
|
+
def _send_template_email(
|
116
|
+
self,
|
117
|
+
client: SendGridAPIClient,
|
118
|
+
config: SendGridConfig,
|
119
|
+
email: str,
|
120
|
+
otp_code: str,
|
121
|
+
template_data: Optional[Dict[str, Any]] = None
|
122
|
+
) -> Tuple[bool, str]:
|
123
|
+
"""Send email using SendGrid dynamic template."""
|
124
|
+
try:
|
125
|
+
# Prepare template data
|
126
|
+
dynamic_data = {
|
127
|
+
'verification_code': otp_code,
|
128
|
+
'user_email': email,
|
129
|
+
'expiry_minutes': 10,
|
130
|
+
'company_name': config.from_name,
|
131
|
+
**config.custom_template_data,
|
132
|
+
**(template_data or {})
|
133
|
+
}
|
134
|
+
|
135
|
+
message = Mail(
|
136
|
+
from_email=(config.from_email, config.from_name),
|
137
|
+
to_emails=email
|
138
|
+
)
|
139
|
+
|
140
|
+
message.template_id = config.otp_template_id
|
141
|
+
message.dynamic_template_data = dynamic_data
|
142
|
+
|
143
|
+
if config.reply_to_email:
|
144
|
+
message.reply_to = config.reply_to_email
|
145
|
+
|
146
|
+
response = client.send(message)
|
147
|
+
|
148
|
+
if response.status_code in [200, 201, 202]:
|
149
|
+
return True, f"OTP sent via email template to {self._mask_identifier(email)}"
|
150
|
+
else:
|
151
|
+
return False, f"SendGrid API error: {response.status_code}"
|
152
|
+
|
153
|
+
except Exception as e:
|
154
|
+
return False, f"Template email error: {e}"
|
155
|
+
|
156
|
+
def _send_simple_email(
|
157
|
+
self,
|
158
|
+
client: SendGridAPIClient,
|
159
|
+
config: SendGridConfig,
|
160
|
+
email: str,
|
161
|
+
otp_code: str,
|
162
|
+
subject: Optional[str] = None
|
163
|
+
) -> Tuple[bool, str]:
|
164
|
+
"""Send simple HTML email without template."""
|
165
|
+
try:
|
166
|
+
html_content = self._generate_html_content(otp_code, config.from_name)
|
167
|
+
plain_content = self._generate_plain_content(otp_code)
|
168
|
+
|
169
|
+
message = Mail(
|
170
|
+
from_email=(config.from_email, config.from_name),
|
171
|
+
to_emails=email,
|
172
|
+
subject=subject or config.default_subject,
|
173
|
+
html_content=html_content,
|
174
|
+
plain_text_content=plain_content
|
175
|
+
)
|
176
|
+
|
177
|
+
if config.reply_to_email:
|
178
|
+
message.reply_to = config.reply_to_email
|
179
|
+
|
180
|
+
response = client.send(message)
|
181
|
+
|
182
|
+
if response.status_code in [200, 201, 202]:
|
183
|
+
return True, f"OTP sent via email to {self._mask_identifier(email)}"
|
184
|
+
else:
|
185
|
+
return False, f"SendGrid API error: {response.status_code}"
|
186
|
+
|
187
|
+
except Exception as e:
|
188
|
+
return False, f"Simple email error: {e}"
|
189
|
+
|
190
|
+
def _generate_html_content(self, otp_code: str, company_name: str) -> str:
|
191
|
+
"""Generate HTML email content."""
|
192
|
+
return f"""
|
193
|
+
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px;">
|
194
|
+
<div style="background-color: #f8f9fa; padding: 30px; border-radius: 10px; text-align: center;">
|
195
|
+
<h1 style="color: #333; margin-bottom: 20px;">Verification Code</h1>
|
196
|
+
<p style="color: #666; font-size: 16px; margin-bottom: 30px;">
|
197
|
+
Your verification code is:
|
198
|
+
</p>
|
199
|
+
<div style="background-color: #007bff; color: white; font-size: 32px; font-weight: bold;
|
200
|
+
padding: 20px; border-radius: 8px; letter-spacing: 5px; margin: 30px 0;">
|
201
|
+
{otp_code}
|
202
|
+
</div>
|
203
|
+
<p style="color: #999; font-size: 14px;">
|
204
|
+
This code expires in 10 minutes<br>
|
205
|
+
If you didn't request this code, please ignore this email
|
206
|
+
</p>
|
207
|
+
<hr style="border: none; border-top: 1px solid #eee; margin: 30px 0;">
|
208
|
+
<p style="color: #999; font-size: 12px;">
|
209
|
+
Sent by {company_name}
|
210
|
+
</p>
|
211
|
+
</div>
|
212
|
+
</div>
|
213
|
+
"""
|
214
|
+
|
215
|
+
def _generate_plain_content(self, otp_code: str) -> str:
|
216
|
+
"""Generate plain text email content."""
|
217
|
+
return f"""
|
218
|
+
Your verification code: {otp_code}
|
219
|
+
|
220
|
+
This code expires in 10 minutes.
|
221
|
+
If you didn't request this code, please ignore this email.
|
222
|
+
""".strip()
|
223
|
+
|
224
|
+
|
225
|
+
__all__ = [
|
226
|
+
"EmailOTPService",
|
227
|
+
]
|
@@ -359,7 +359,7 @@ class SendGridService(BaseCfgModule):
|
|
359
359
|
"""Send simple HTML email using Django templates."""
|
360
360
|
try:
|
361
361
|
# Get current config for template context
|
362
|
-
from django_cfg.core.
|
362
|
+
from django_cfg.core.state import get_current_config
|
363
363
|
current_config = get_current_config()
|
364
364
|
|
365
365
|
# Prepare context for unified template
|
@@ -7,8 +7,7 @@ without the complexity of the OTP system.
|
|
7
7
|
|
8
8
|
import logging
|
9
9
|
from typing import Optional, Dict, Any
|
10
|
-
from
|
11
|
-
from twilio.base.exceptions import TwilioException
|
10
|
+
from ._imports import Client, TwilioException
|
12
11
|
|
13
12
|
from django_cfg.modules.base import BaseCfgModule
|
14
13
|
from django_cfg.modules.django_twilio.models import TwilioConfig
|
@@ -0,0 +1,94 @@
|
|
1
|
+
"""
|
2
|
+
SMS OTP service using Twilio Verify API.
|
3
|
+
|
4
|
+
Provides reliable SMS OTP delivery with comprehensive
|
5
|
+
error handling and international support.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import logging
|
9
|
+
from typing import Tuple
|
10
|
+
from asgiref.sync import sync_to_async
|
11
|
+
|
12
|
+
from .base import BaseTwilioService
|
13
|
+
from .exceptions import (
|
14
|
+
TwilioConfigurationError,
|
15
|
+
TwilioSendError,
|
16
|
+
)
|
17
|
+
|
18
|
+
logger = logging.getLogger(__name__)
|
19
|
+
|
20
|
+
|
21
|
+
class SMSOTPService(BaseTwilioService):
|
22
|
+
"""
|
23
|
+
SMS OTP service using Twilio Verify API.
|
24
|
+
|
25
|
+
Provides reliable SMS OTP delivery with comprehensive
|
26
|
+
error handling and international support.
|
27
|
+
"""
|
28
|
+
|
29
|
+
def send_otp(self, phone_number: str) -> Tuple[bool, str]:
|
30
|
+
"""
|
31
|
+
Send OTP via SMS.
|
32
|
+
|
33
|
+
Args:
|
34
|
+
phone_number: Phone number in E.164 format
|
35
|
+
|
36
|
+
Returns:
|
37
|
+
Tuple[bool, str]: (success, message)
|
38
|
+
|
39
|
+
Raises:
|
40
|
+
TwilioConfigurationError: If Verify service not configured
|
41
|
+
TwilioSendError: If SMS sending fails
|
42
|
+
"""
|
43
|
+
config = self.get_twilio_config()
|
44
|
+
|
45
|
+
if not config.verify:
|
46
|
+
raise TwilioConfigurationError(
|
47
|
+
"Twilio Verify service not configured",
|
48
|
+
missing_fields=["verify"],
|
49
|
+
suggestions=["Configure TwilioVerifyConfig in your Twilio settings"]
|
50
|
+
)
|
51
|
+
|
52
|
+
client = self.get_twilio_client()
|
53
|
+
|
54
|
+
try:
|
55
|
+
verification = client.verify.v2.services(
|
56
|
+
config.verify.service_sid
|
57
|
+
).verifications.create(
|
58
|
+
to=phone_number,
|
59
|
+
channel='sms'
|
60
|
+
)
|
61
|
+
|
62
|
+
if verification.status == 'pending':
|
63
|
+
logger.info(f"SMS OTP sent successfully to {self._mask_identifier(phone_number)}")
|
64
|
+
return True, f"OTP sent via SMS to {self._mask_identifier(phone_number)}"
|
65
|
+
else:
|
66
|
+
raise TwilioSendError(
|
67
|
+
f"SMS OTP failed with status: {verification.status}",
|
68
|
+
channel="sms",
|
69
|
+
recipient=phone_number
|
70
|
+
)
|
71
|
+
|
72
|
+
except TwilioException as e:
|
73
|
+
raise TwilioSendError(
|
74
|
+
f"SMS OTP failed: {e}",
|
75
|
+
channel="sms",
|
76
|
+
recipient=phone_number,
|
77
|
+
twilio_error_code=getattr(e, 'code', None),
|
78
|
+
twilio_error_message=str(e)
|
79
|
+
) from e
|
80
|
+
except Exception as e:
|
81
|
+
raise TwilioSendError(
|
82
|
+
f"Unexpected error sending SMS OTP: {e}",
|
83
|
+
channel="sms",
|
84
|
+
recipient=phone_number
|
85
|
+
) from e
|
86
|
+
|
87
|
+
async def asend_otp(self, phone_number: str) -> Tuple[bool, str]:
|
88
|
+
"""Async version of send_otp."""
|
89
|
+
return await sync_to_async(self.send_otp)(phone_number)
|
90
|
+
|
91
|
+
|
92
|
+
__all__ = [
|
93
|
+
"SMSOTPService",
|
94
|
+
]
|
@@ -13,8 +13,7 @@ import string
|
|
13
13
|
from typing import Optional, Dict, Any, Tuple
|
14
14
|
from datetime import datetime, timedelta
|
15
15
|
|
16
|
-
from
|
17
|
-
from twilio.base.exceptions import TwilioException
|
16
|
+
from ._imports import Client, TwilioException
|
18
17
|
from asgiref.sync import sync_to_async
|
19
18
|
|
20
19
|
from django_cfg.modules.base import BaseCfgModule
|
@@ -256,7 +255,7 @@ class TwilioService(BaseCfgModule):
|
|
256
255
|
|
257
256
|
# STEP 1: Try Direct WhatsApp with custom OTP
|
258
257
|
try:
|
259
|
-
from django_cfg.core.
|
258
|
+
from django_cfg.core.state import get_current_config
|
260
259
|
django_config = get_current_config()
|
261
260
|
site_name = django_config.project_name if django_config else "Django CFG"
|
262
261
|
|