django-cfg 1.3.5__py3-none-any.whl → 1.3.9__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/__init__.py +24 -8
- django_cfg/apps/accounts/admin/activity_admin.py +146 -0
- django_cfg/apps/accounts/admin/filters.py +98 -22
- django_cfg/apps/accounts/admin/group_admin.py +86 -0
- django_cfg/apps/accounts/admin/inlines.py +42 -13
- django_cfg/apps/accounts/admin/otp_admin.py +115 -0
- django_cfg/apps/accounts/admin/registration_admin.py +173 -0
- django_cfg/apps/accounts/admin/resources.py +123 -19
- django_cfg/apps/accounts/admin/twilio_admin.py +327 -0
- django_cfg/apps/accounts/admin/user_admin.py +362 -0
- django_cfg/apps/agents/admin/__init__.py +17 -4
- django_cfg/apps/agents/admin/execution_admin.py +204 -183
- django_cfg/apps/agents/admin/registry_admin.py +230 -255
- django_cfg/apps/agents/admin/toolsets_admin.py +274 -321
- django_cfg/apps/agents/core/__init__.py +1 -1
- django_cfg/apps/agents/core/django_agent.py +221 -0
- django_cfg/apps/agents/core/exceptions.py +14 -0
- django_cfg/apps/agents/core/orchestrator.py +18 -3
- django_cfg/apps/knowbase/admin/__init__.py +1 -1
- django_cfg/apps/knowbase/admin/archive_admin.py +352 -640
- django_cfg/apps/knowbase/admin/chat_admin.py +258 -192
- django_cfg/apps/knowbase/admin/document_admin.py +269 -262
- django_cfg/apps/knowbase/admin/external_data_admin.py +271 -489
- django_cfg/apps/knowbase/config/settings.py +21 -4
- django_cfg/apps/knowbase/views/chat_views.py +3 -0
- django_cfg/apps/leads/admin/__init__.py +3 -1
- django_cfg/apps/leads/admin/leads_admin.py +235 -35
- django_cfg/apps/maintenance/admin/__init__.py +2 -2
- django_cfg/apps/maintenance/admin/api_key_admin.py +125 -63
- django_cfg/apps/maintenance/admin/log_admin.py +143 -61
- django_cfg/apps/maintenance/admin/scheduled_admin.py +212 -301
- django_cfg/apps/maintenance/admin/site_admin.py +213 -352
- django_cfg/apps/newsletter/admin/__init__.py +29 -2
- django_cfg/apps/newsletter/admin/newsletter_admin.py +531 -193
- django_cfg/apps/payments/admin/__init__.py +18 -27
- django_cfg/apps/payments/admin/api_keys_admin.py +179 -546
- django_cfg/apps/payments/admin/balance_admin.py +166 -632
- django_cfg/apps/payments/admin/currencies_admin.py +235 -607
- django_cfg/apps/payments/admin/endpoint_groups_admin.py +127 -0
- django_cfg/apps/payments/admin/filters.py +83 -3
- django_cfg/apps/payments/admin/networks_admin.py +258 -0
- django_cfg/apps/payments/admin/payments_admin.py +171 -461
- django_cfg/apps/payments/admin/subscriptions_admin.py +119 -636
- django_cfg/apps/payments/admin/tariffs_admin.py +248 -0
- django_cfg/apps/payments/admin_interface/serializers/payment_serializers.py +105 -34
- django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +12 -16
- django_cfg/apps/payments/admin_interface/views/__init__.py +2 -0
- django_cfg/apps/payments/admin_interface/views/api/webhook_admin.py +13 -18
- django_cfg/apps/payments/management/commands/manage_currencies.py +236 -274
- django_cfg/apps/payments/management/commands/manage_providers.py +4 -1
- django_cfg/apps/payments/middleware/api_access.py +32 -6
- django_cfg/apps/payments/migrations/0002_currency_usd_rate_currency_usd_rate_updated_at.py +26 -0
- django_cfg/apps/payments/migrations/0003_remove_provider_currency_fields.py +28 -0
- django_cfg/apps/payments/migrations/0004_add_reserved_usd_field.py +30 -0
- django_cfg/apps/payments/models/balance.py +12 -0
- django_cfg/apps/payments/models/currencies.py +106 -32
- django_cfg/apps/payments/models/managers/currency_managers.py +65 -0
- django_cfg/apps/payments/services/core/currency_service.py +35 -28
- django_cfg/apps/payments/services/core/payment_service.py +1 -1
- django_cfg/apps/payments/services/providers/__init__.py +3 -0
- django_cfg/apps/payments/services/providers/base.py +95 -39
- django_cfg/apps/payments/services/providers/models/__init__.py +40 -0
- django_cfg/apps/payments/services/providers/models/base.py +122 -0
- django_cfg/apps/payments/services/providers/models/providers.py +87 -0
- django_cfg/apps/payments/services/providers/models/universal.py +48 -0
- django_cfg/apps/payments/services/providers/nowpayments/__init__.py +31 -0
- django_cfg/apps/payments/services/providers/nowpayments/config.py +70 -0
- django_cfg/apps/payments/services/providers/nowpayments/models.py +150 -0
- django_cfg/apps/payments/services/providers/nowpayments/parsers.py +879 -0
- django_cfg/apps/payments/services/providers/{nowpayments.py → nowpayments/provider.py} +240 -209
- django_cfg/apps/payments/services/providers/nowpayments/sync.py +196 -0
- django_cfg/apps/payments/services/providers/registry.py +4 -32
- django_cfg/apps/payments/services/providers/sync_service.py +277 -0
- django_cfg/apps/payments/static/payments/js/api-client.js +23 -5
- django_cfg/apps/payments/static/payments/js/payment-form.js +65 -8
- django_cfg/apps/payments/tasks/__init__.py +39 -0
- django_cfg/apps/payments/tasks/types.py +73 -0
- django_cfg/apps/payments/tasks/usage_tracking.py +308 -0
- django_cfg/apps/payments/templates/admin/payments/_components/dashboard_header.html +23 -0
- django_cfg/apps/payments/templates/admin/payments/_components/stats_card.html +25 -0
- django_cfg/apps/payments/templates/admin/payments/_components/stats_grid.html +16 -0
- django_cfg/apps/payments/templates/admin/payments/apikey/change_list.html +39 -0
- django_cfg/apps/payments/templates/admin/payments/balance/change_list.html +50 -0
- django_cfg/apps/payments/templates/admin/payments/currency/change_list.html +40 -0
- django_cfg/apps/payments/templates/admin/payments/payment/change_list.html +48 -0
- django_cfg/apps/payments/templates/admin/payments/subscription/change_list.html +48 -0
- django_cfg/apps/payments/urls_admin.py +1 -1
- django_cfg/apps/payments/views/api/currencies.py +5 -5
- django_cfg/apps/payments/views/overview/services.py +2 -2
- django_cfg/apps/payments/views/serializers/currencies.py +4 -3
- django_cfg/apps/support/admin/__init__.py +10 -1
- django_cfg/apps/support/admin/support_admin.py +338 -141
- django_cfg/apps/tasks/admin/__init__.py +11 -0
- django_cfg/apps/tasks/admin/tasks_admin.py +430 -0
- django_cfg/apps/urls.py +1 -2
- django_cfg/config.py +1 -1
- django_cfg/core/config.py +10 -5
- django_cfg/core/generation.py +1 -1
- django_cfg/management/commands/__init__.py +13 -1
- django_cfg/management/commands/app_agent_diagnose.py +470 -0
- django_cfg/management/commands/app_agent_generate.py +342 -0
- django_cfg/management/commands/app_agent_info.py +308 -0
- django_cfg/management/commands/migrate_all.py +9 -3
- django_cfg/management/commands/migrator.py +11 -6
- django_cfg/management/commands/rundramatiq.py +3 -2
- django_cfg/middleware/__init__.py +0 -2
- django_cfg/models/api_keys.py +115 -0
- django_cfg/modules/django_admin/__init__.py +64 -0
- django_cfg/modules/django_admin/decorators/__init__.py +13 -0
- django_cfg/modules/django_admin/decorators/actions.py +106 -0
- django_cfg/modules/django_admin/decorators/display.py +106 -0
- django_cfg/modules/django_admin/mixins/__init__.py +14 -0
- django_cfg/modules/django_admin/mixins/display_mixin.py +81 -0
- django_cfg/modules/django_admin/mixins/optimization_mixin.py +41 -0
- django_cfg/modules/django_admin/mixins/standalone_actions_mixin.py +202 -0
- django_cfg/modules/django_admin/models/__init__.py +20 -0
- django_cfg/modules/django_admin/models/action_models.py +33 -0
- django_cfg/modules/django_admin/models/badge_models.py +20 -0
- django_cfg/modules/django_admin/models/base.py +26 -0
- django_cfg/modules/django_admin/models/display_models.py +31 -0
- django_cfg/modules/django_admin/utils/badges.py +159 -0
- django_cfg/modules/django_admin/utils/displays.py +247 -0
- django_cfg/modules/django_app_agent/__init__.py +87 -0
- django_cfg/modules/django_app_agent/agents/__init__.py +40 -0
- django_cfg/modules/django_app_agent/agents/base/__init__.py +24 -0
- django_cfg/modules/django_app_agent/agents/base/agent.py +354 -0
- django_cfg/modules/django_app_agent/agents/base/context.py +236 -0
- django_cfg/modules/django_app_agent/agents/base/executor.py +430 -0
- django_cfg/modules/django_app_agent/agents/generation/__init__.py +12 -0
- django_cfg/modules/django_app_agent/agents/generation/app_generator/__init__.py +15 -0
- django_cfg/modules/django_app_agent/agents/generation/app_generator/config_validator.py +147 -0
- django_cfg/modules/django_app_agent/agents/generation/app_generator/main.py +99 -0
- django_cfg/modules/django_app_agent/agents/generation/app_generator/models.py +32 -0
- django_cfg/modules/django_app_agent/agents/generation/app_generator/prompt_manager.py +290 -0
- django_cfg/modules/django_app_agent/agents/interfaces.py +376 -0
- django_cfg/modules/django_app_agent/core/__init__.py +33 -0
- django_cfg/modules/django_app_agent/core/config.py +300 -0
- django_cfg/modules/django_app_agent/core/exceptions.py +359 -0
- django_cfg/modules/django_app_agent/models/__init__.py +71 -0
- django_cfg/modules/django_app_agent/models/base.py +283 -0
- django_cfg/modules/django_app_agent/models/context.py +496 -0
- django_cfg/modules/django_app_agent/models/enums.py +481 -0
- django_cfg/modules/django_app_agent/models/requests.py +500 -0
- django_cfg/modules/django_app_agent/models/responses.py +585 -0
- django_cfg/modules/django_app_agent/pytest.ini +6 -0
- django_cfg/modules/django_app_agent/services/__init__.py +42 -0
- django_cfg/modules/django_app_agent/services/app_generator/__init__.py +30 -0
- django_cfg/modules/django_app_agent/services/app_generator/ai_integration.py +133 -0
- django_cfg/modules/django_app_agent/services/app_generator/context.py +40 -0
- django_cfg/modules/django_app_agent/services/app_generator/main.py +202 -0
- django_cfg/modules/django_app_agent/services/app_generator/structure.py +316 -0
- django_cfg/modules/django_app_agent/services/app_generator/validation.py +125 -0
- django_cfg/modules/django_app_agent/services/base.py +437 -0
- django_cfg/modules/django_app_agent/services/context_builder/__init__.py +34 -0
- django_cfg/modules/django_app_agent/services/context_builder/code_extractor.py +141 -0
- django_cfg/modules/django_app_agent/services/context_builder/context_generator.py +276 -0
- django_cfg/modules/django_app_agent/services/context_builder/main.py +272 -0
- django_cfg/modules/django_app_agent/services/context_builder/models.py +40 -0
- django_cfg/modules/django_app_agent/services/context_builder/pattern_analyzer.py +85 -0
- django_cfg/modules/django_app_agent/services/project_scanner/__init__.py +31 -0
- django_cfg/modules/django_app_agent/services/project_scanner/app_discovery.py +311 -0
- django_cfg/modules/django_app_agent/services/project_scanner/main.py +221 -0
- django_cfg/modules/django_app_agent/services/project_scanner/models.py +59 -0
- django_cfg/modules/django_app_agent/services/project_scanner/pattern_detection.py +94 -0
- django_cfg/modules/django_app_agent/services/questioning_service/__init__.py +28 -0
- django_cfg/modules/django_app_agent/services/questioning_service/main.py +273 -0
- django_cfg/modules/django_app_agent/services/questioning_service/models.py +111 -0
- django_cfg/modules/django_app_agent/services/questioning_service/question_generator.py +251 -0
- django_cfg/modules/django_app_agent/services/questioning_service/response_processor.py +347 -0
- django_cfg/modules/django_app_agent/services/questioning_service/session_manager.py +356 -0
- django_cfg/modules/django_app_agent/services/report_service.py +332 -0
- django_cfg/modules/django_app_agent/services/template_manager/__init__.py +18 -0
- django_cfg/modules/django_app_agent/services/template_manager/jinja_engine.py +236 -0
- django_cfg/modules/django_app_agent/services/template_manager/main.py +159 -0
- django_cfg/modules/django_app_agent/services/template_manager/models.py +36 -0
- django_cfg/modules/django_app_agent/services/template_manager/template_loader.py +100 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/admin.py.j2 +105 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/apps.py.j2 +31 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/cfg_config.py.j2 +44 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/cfg_module.py.j2 +81 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/forms.py.j2 +107 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/models.py.j2 +139 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/serializers.py.j2 +91 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/tests.py.j2 +195 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/urls.py.j2 +35 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/views.py.j2 +211 -0
- django_cfg/modules/django_app_agent/services/template_manager/variable_processor.py +200 -0
- django_cfg/modules/django_app_agent/services/validation_service/__init__.py +25 -0
- django_cfg/modules/django_app_agent/services/validation_service/django_validator.py +333 -0
- django_cfg/modules/django_app_agent/services/validation_service/main.py +242 -0
- django_cfg/modules/django_app_agent/services/validation_service/models.py +66 -0
- django_cfg/modules/django_app_agent/services/validation_service/quality_validator.py +352 -0
- django_cfg/modules/django_app_agent/services/validation_service/security_validator.py +272 -0
- django_cfg/modules/django_app_agent/services/validation_service/syntax_validator.py +203 -0
- django_cfg/modules/django_app_agent/ui/__init__.py +25 -0
- django_cfg/modules/django_app_agent/ui/cli.py +419 -0
- django_cfg/modules/django_app_agent/ui/rich_components.py +622 -0
- django_cfg/modules/django_app_agent/utils/__init__.py +38 -0
- django_cfg/modules/django_app_agent/utils/logging.py +360 -0
- django_cfg/modules/django_app_agent/utils/validation.py +417 -0
- django_cfg/modules/django_currency/__init__.py +2 -2
- django_cfg/modules/django_currency/clients/__init__.py +2 -2
- django_cfg/modules/django_currency/clients/hybrid_client.py +587 -0
- django_cfg/modules/django_currency/core/converter.py +12 -12
- django_cfg/modules/django_currency/database/__init__.py +2 -2
- django_cfg/modules/django_currency/database/database_loader.py +93 -42
- django_cfg/modules/django_llm/llm/client.py +10 -2
- django_cfg/modules/django_unfold/callbacks/actions.py +1 -1
- django_cfg/modules/django_unfold/callbacks/statistics.py +1 -1
- django_cfg/modules/django_unfold/dashboard.py +14 -13
- django_cfg/modules/django_unfold/models/config.py +1 -1
- django_cfg/registry/core.py +3 -0
- django_cfg/registry/third_party.py +2 -2
- django_cfg/template_archive/django_sample.zip +0 -0
- {django_cfg-1.3.5.dist-info → django_cfg-1.3.9.dist-info}/METADATA +2 -1
- {django_cfg-1.3.5.dist-info → django_cfg-1.3.9.dist-info}/RECORD +224 -118
- django_cfg/apps/accounts/admin/activity.py +0 -96
- django_cfg/apps/accounts/admin/group.py +0 -17
- django_cfg/apps/accounts/admin/otp.py +0 -59
- django_cfg/apps/accounts/admin/registration_source.py +0 -97
- django_cfg/apps/accounts/admin/twilio_response.py +0 -227
- django_cfg/apps/accounts/admin/user.py +0 -300
- django_cfg/apps/agents/core/agent.py +0 -281
- django_cfg/apps/payments/admin_interface/old/payments/base.html +0 -175
- django_cfg/apps/payments/admin_interface/old/payments/components/dev_tool_card.html +0 -125
- django_cfg/apps/payments/admin_interface/old/payments/components/loading_spinner.html +0 -16
- django_cfg/apps/payments/admin_interface/old/payments/components/ngrok_status_card.html +0 -113
- django_cfg/apps/payments/admin_interface/old/payments/components/notification.html +0 -27
- django_cfg/apps/payments/admin_interface/old/payments/components/provider_card.html +0 -86
- django_cfg/apps/payments/admin_interface/old/payments/components/status_card.html +0 -35
- django_cfg/apps/payments/admin_interface/old/payments/currency_converter.html +0 -382
- django_cfg/apps/payments/admin_interface/old/payments/payment_dashboard.html +0 -309
- django_cfg/apps/payments/admin_interface/old/payments/payment_form.html +0 -303
- django_cfg/apps/payments/admin_interface/old/payments/payment_list.html +0 -382
- django_cfg/apps/payments/admin_interface/old/payments/payment_status.html +0 -500
- django_cfg/apps/payments/admin_interface/old/payments/webhook_dashboard.html +0 -518
- django_cfg/apps/payments/admin_interface/old/static/payments/css/components.css +0 -619
- django_cfg/apps/payments/admin_interface/old/static/payments/css/dashboard.css +0 -188
- django_cfg/apps/payments/admin_interface/old/static/payments/js/components.js +0 -545
- django_cfg/apps/payments/admin_interface/old/static/payments/js/ngrok-status.js +0 -163
- django_cfg/apps/payments/admin_interface/old/static/payments/js/utils.js +0 -412
- django_cfg/apps/tasks/admin.py +0 -320
- django_cfg/middleware/static_nocache.py +0 -55
- django_cfg/modules/django_currency/clients/yahoo_client.py +0 -157
- /django_cfg/modules/{django_unfold → django_admin}/icons/README.md +0 -0
- /django_cfg/modules/{django_unfold → django_admin}/icons/__init__.py +0 -0
- /django_cfg/modules/{django_unfold → django_admin}/icons/constants.py +0 -0
- /django_cfg/modules/{django_unfold → django_admin}/icons/generate_icons.py +0 -0
- {django_cfg-1.3.5.dist-info → django_cfg-1.3.9.dist-info}/WHEEL +0 -0
- {django_cfg-1.3.5.dist-info → django_cfg-1.3.9.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.3.5.dist-info → django_cfg-1.3.9.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,115 @@
|
|
1
|
+
"""
|
2
|
+
OTP admin interface using Django Admin Utilities.
|
3
|
+
|
4
|
+
Enhanced OTP management with status indicators and time displays.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from django.contrib import admin
|
8
|
+
from unfold.admin import ModelAdmin
|
9
|
+
|
10
|
+
from django_cfg.modules.django_admin import (
|
11
|
+
OptimizedModelAdmin,
|
12
|
+
DisplayMixin,
|
13
|
+
DateTimeDisplayConfig,
|
14
|
+
StatusBadgeConfig,
|
15
|
+
Icons,
|
16
|
+
display
|
17
|
+
)
|
18
|
+
from django_cfg.modules.django_admin.utils.badges import StatusBadge
|
19
|
+
|
20
|
+
from ..models import OTPSecret
|
21
|
+
from .filters import OTPStatusFilter
|
22
|
+
|
23
|
+
|
24
|
+
@admin.register(OTPSecret)
|
25
|
+
class OTPSecretAdmin(OptimizedModelAdmin, DisplayMixin, ModelAdmin):
|
26
|
+
"""Enhanced OTP admin using Django Admin Utilities."""
|
27
|
+
|
28
|
+
list_display = [
|
29
|
+
'email_display',
|
30
|
+
'secret_display',
|
31
|
+
'status_display',
|
32
|
+
'created_display',
|
33
|
+
'expires_display'
|
34
|
+
]
|
35
|
+
list_display_links = ['email_display', 'secret_display']
|
36
|
+
list_filter = [OTPStatusFilter, 'is_used', 'created_at']
|
37
|
+
search_fields = ['email', 'secret']
|
38
|
+
readonly_fields = ['created_at', 'expires_at']
|
39
|
+
ordering = ['-created_at']
|
40
|
+
|
41
|
+
fieldsets = (
|
42
|
+
(
|
43
|
+
"OTP Details",
|
44
|
+
{
|
45
|
+
"fields": ("email", "secret", "is_used"),
|
46
|
+
},
|
47
|
+
),
|
48
|
+
(
|
49
|
+
"Timestamps",
|
50
|
+
{
|
51
|
+
"fields": ("created_at", "expires_at"),
|
52
|
+
"classes": ("collapse",),
|
53
|
+
},
|
54
|
+
),
|
55
|
+
)
|
56
|
+
|
57
|
+
@display(description="Email")
|
58
|
+
def email_display(self, obj):
|
59
|
+
"""Email display with email icon."""
|
60
|
+
config = StatusBadgeConfig(show_icons=True, icon=Icons.EMAIL)
|
61
|
+
return StatusBadge.create(
|
62
|
+
text=obj.email,
|
63
|
+
variant="info",
|
64
|
+
config=config
|
65
|
+
)
|
66
|
+
|
67
|
+
@display(description="Secret")
|
68
|
+
def secret_display(self, obj):
|
69
|
+
"""Secret display with key icon."""
|
70
|
+
config = StatusBadgeConfig(show_icons=True, icon=Icons.KEY)
|
71
|
+
return StatusBadge.create(
|
72
|
+
text=obj.secret,
|
73
|
+
variant="secondary",
|
74
|
+
config=config
|
75
|
+
)
|
76
|
+
|
77
|
+
@display(description="Status", label=True)
|
78
|
+
def status_display(self, obj):
|
79
|
+
"""Enhanced OTP status with appropriate icons and colors."""
|
80
|
+
if obj.is_used:
|
81
|
+
status = "Used"
|
82
|
+
icon = Icons.CHECK_CIRCLE
|
83
|
+
variant = "secondary"
|
84
|
+
elif obj.is_valid:
|
85
|
+
status = "Valid"
|
86
|
+
icon = Icons.VERIFIED
|
87
|
+
variant = "success"
|
88
|
+
else:
|
89
|
+
status = "Expired"
|
90
|
+
icon = Icons.SCHEDULE
|
91
|
+
variant = "warning"
|
92
|
+
|
93
|
+
config = StatusBadgeConfig(
|
94
|
+
show_icons=True,
|
95
|
+
icon=icon,
|
96
|
+
custom_mappings={status: variant}
|
97
|
+
)
|
98
|
+
|
99
|
+
return self.display_status_auto(
|
100
|
+
type('obj', (), {'status': status})(),
|
101
|
+
'status',
|
102
|
+
config
|
103
|
+
)
|
104
|
+
|
105
|
+
@display(description="Created")
|
106
|
+
def created_display(self, obj):
|
107
|
+
"""Created time with relative display."""
|
108
|
+
config = DateTimeDisplayConfig(show_relative=True)
|
109
|
+
return self.display_datetime_relative(obj, 'created_at', config)
|
110
|
+
|
111
|
+
@display(description="Expires")
|
112
|
+
def expires_display(self, obj):
|
113
|
+
"""Expires time with relative display."""
|
114
|
+
config = DateTimeDisplayConfig(show_relative=True)
|
115
|
+
return self.display_datetime_relative(obj, 'expires_at', config)
|
@@ -0,0 +1,173 @@
|
|
1
|
+
"""
|
2
|
+
Registration admin interfaces using Django Admin Utilities.
|
3
|
+
|
4
|
+
Enhanced registration source management with Material Icons and optimized queries.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from django.contrib import admin
|
8
|
+
from unfold.admin import ModelAdmin
|
9
|
+
|
10
|
+
from django_cfg.modules.django_admin import (
|
11
|
+
OptimizedModelAdmin,
|
12
|
+
DisplayMixin,
|
13
|
+
UserDisplayConfig,
|
14
|
+
DateTimeDisplayConfig,
|
15
|
+
StatusBadgeConfig,
|
16
|
+
Icons,
|
17
|
+
display
|
18
|
+
)
|
19
|
+
from django_cfg.modules.django_admin.utils.badges import StatusBadge
|
20
|
+
|
21
|
+
from ..models import RegistrationSource, UserRegistrationSource
|
22
|
+
|
23
|
+
|
24
|
+
@admin.register(RegistrationSource)
|
25
|
+
class RegistrationSourceAdmin(OptimizedModelAdmin, DisplayMixin, ModelAdmin):
|
26
|
+
"""Enhanced admin for RegistrationSource model using Django Admin Utilities."""
|
27
|
+
|
28
|
+
list_display = [
|
29
|
+
'name_display',
|
30
|
+
'description_display',
|
31
|
+
'is_active_display',
|
32
|
+
'users_count_display',
|
33
|
+
'created_at_display'
|
34
|
+
]
|
35
|
+
list_display_links = ['name_display']
|
36
|
+
list_filter = ['is_active', 'created_at']
|
37
|
+
search_fields = ['name', 'description']
|
38
|
+
readonly_fields = ['created_at', 'updated_at']
|
39
|
+
ordering = ['name']
|
40
|
+
|
41
|
+
fieldsets = (
|
42
|
+
('Source Details', {
|
43
|
+
'fields': ('name', 'description', 'is_active')
|
44
|
+
}),
|
45
|
+
('Timestamps', {
|
46
|
+
'fields': ('created_at', 'updated_at'),
|
47
|
+
'classes': ('collapse',)
|
48
|
+
}),
|
49
|
+
)
|
50
|
+
|
51
|
+
@display(description="Name")
|
52
|
+
def name_display(self, obj):
|
53
|
+
"""Name display with source icon."""
|
54
|
+
config = StatusBadgeConfig(show_icons=True, icon=Icons.SOURCE)
|
55
|
+
return StatusBadge.create(
|
56
|
+
text=obj.name,
|
57
|
+
variant="primary",
|
58
|
+
config=config
|
59
|
+
)
|
60
|
+
|
61
|
+
@display(description="Description")
|
62
|
+
def description_display(self, obj):
|
63
|
+
"""Description display with info icon."""
|
64
|
+
if not obj.description:
|
65
|
+
return "—"
|
66
|
+
|
67
|
+
# Truncate long descriptions
|
68
|
+
description = obj.description
|
69
|
+
if len(description) > 50:
|
70
|
+
description = f"{description[:47]}..."
|
71
|
+
|
72
|
+
config = StatusBadgeConfig(show_icons=True, icon=Icons.INFO)
|
73
|
+
return StatusBadge.create(
|
74
|
+
text=description,
|
75
|
+
variant="info",
|
76
|
+
config=config
|
77
|
+
)
|
78
|
+
|
79
|
+
@display(description="Status", label=True)
|
80
|
+
def is_active_display(self, obj):
|
81
|
+
"""Active status display."""
|
82
|
+
status = "Active" if obj.is_active else "Inactive"
|
83
|
+
icon = Icons.CHECK_CIRCLE if obj.is_active else Icons.CANCEL
|
84
|
+
variant = "success" if obj.is_active else "secondary"
|
85
|
+
|
86
|
+
config = StatusBadgeConfig(
|
87
|
+
show_icons=True,
|
88
|
+
icon=icon,
|
89
|
+
custom_mappings={status: variant}
|
90
|
+
)
|
91
|
+
|
92
|
+
return self.display_status_auto(
|
93
|
+
type('obj', (), {'status': status})(),
|
94
|
+
'status',
|
95
|
+
config
|
96
|
+
)
|
97
|
+
|
98
|
+
@display(description="Users")
|
99
|
+
def users_count_display(self, obj):
|
100
|
+
"""Count of users from this source."""
|
101
|
+
count = obj.user_registration_sources.count()
|
102
|
+
if count == 0:
|
103
|
+
return "—"
|
104
|
+
|
105
|
+
config = StatusBadgeConfig(show_icons=True, icon=Icons.PEOPLE)
|
106
|
+
return StatusBadge.create(
|
107
|
+
text=f"{count} user{'s' if count != 1 else ''}",
|
108
|
+
variant="info",
|
109
|
+
config=config
|
110
|
+
)
|
111
|
+
|
112
|
+
@display(description="Created")
|
113
|
+
def created_at_display(self, obj):
|
114
|
+
"""Created time with relative display."""
|
115
|
+
config = DateTimeDisplayConfig(show_relative=True)
|
116
|
+
return self.display_datetime_relative(obj, 'created_at', config)
|
117
|
+
|
118
|
+
|
119
|
+
@admin.register(UserRegistrationSource)
|
120
|
+
class UserRegistrationSourceAdmin(OptimizedModelAdmin, DisplayMixin, ModelAdmin):
|
121
|
+
"""Enhanced admin for UserRegistrationSource model using Django Admin Utilities."""
|
122
|
+
|
123
|
+
# Performance optimization
|
124
|
+
select_related_fields = ['user', 'source']
|
125
|
+
|
126
|
+
list_display = [
|
127
|
+
'user_display',
|
128
|
+
'source_display',
|
129
|
+
'registration_date_display'
|
130
|
+
]
|
131
|
+
list_display_links = ['user_display', 'source_display']
|
132
|
+
list_filter = ['source', 'registration_date']
|
133
|
+
search_fields = ['user__email', 'user__first_name', 'user__last_name', 'source__name']
|
134
|
+
readonly_fields = ['registration_date']
|
135
|
+
date_hierarchy = 'registration_date'
|
136
|
+
ordering = ['-registration_date']
|
137
|
+
|
138
|
+
fieldsets = (
|
139
|
+
('Registration Details', {
|
140
|
+
'fields': ('user', 'source', 'first_registration')
|
141
|
+
}),
|
142
|
+
('Timestamp', {
|
143
|
+
'fields': ('registration_date',)
|
144
|
+
}),
|
145
|
+
)
|
146
|
+
|
147
|
+
@display(description="User")
|
148
|
+
def user_display(self, obj):
|
149
|
+
"""User display with avatar."""
|
150
|
+
config = UserDisplayConfig(
|
151
|
+
show_avatar=True,
|
152
|
+
avatar_size=20,
|
153
|
+
show_email=True
|
154
|
+
)
|
155
|
+
return self.display_user_simple(obj.user, config)
|
156
|
+
|
157
|
+
@display(description="Source")
|
158
|
+
def source_display(self, obj):
|
159
|
+
"""Source display with source icon."""
|
160
|
+
config = StatusBadgeConfig(show_icons=True, icon=Icons.SOURCE)
|
161
|
+
variant = "success" if obj.source.is_active else "secondary"
|
162
|
+
|
163
|
+
return StatusBadge.create(
|
164
|
+
text=obj.source.name,
|
165
|
+
variant=variant,
|
166
|
+
config=config
|
167
|
+
)
|
168
|
+
|
169
|
+
@display(description="Registered")
|
170
|
+
def registration_date_display(self, obj):
|
171
|
+
"""Registration date with relative display."""
|
172
|
+
config = DateTimeDisplayConfig(show_relative=True)
|
173
|
+
return self.display_datetime_relative(obj, 'registration_date', config)
|
@@ -1,16 +1,20 @@
|
|
1
1
|
"""
|
2
2
|
Import/Export resources for Accounts app.
|
3
|
+
|
4
|
+
Enhanced resources with better data validation and export optimization.
|
3
5
|
"""
|
4
6
|
|
5
7
|
from import_export import resources, fields
|
6
8
|
from import_export.widgets import ForeignKeyWidget, DateTimeWidget, BooleanWidget, ManyToManyWidget
|
7
9
|
from django.contrib.auth.models import Group
|
10
|
+
from django.utils import timezone
|
11
|
+
from datetime import timedelta
|
8
12
|
|
9
13
|
from ..models import CustomUser, UserActivity, RegistrationSource, TwilioResponse
|
10
14
|
|
11
15
|
|
12
16
|
class CustomUserResource(resources.ModelResource):
|
13
|
-
"""
|
17
|
+
"""Enhanced resource for importing/exporting users."""
|
14
18
|
|
15
19
|
# Custom fields for better export/import
|
16
20
|
full_name = fields.Field(
|
@@ -54,6 +58,17 @@ class CustomUserResource(resources.ModelResource):
|
|
54
58
|
attribute='phone_verified',
|
55
59
|
widget=BooleanWidget()
|
56
60
|
)
|
61
|
+
|
62
|
+
# Additional computed fields
|
63
|
+
registration_sources = fields.Field(
|
64
|
+
column_name='registration_sources',
|
65
|
+
readonly=True
|
66
|
+
)
|
67
|
+
|
68
|
+
activities_count = fields.Field(
|
69
|
+
column_name='activities_count',
|
70
|
+
readonly=True
|
71
|
+
)
|
57
72
|
|
58
73
|
class Meta:
|
59
74
|
model = CustomUser
|
@@ -71,6 +86,8 @@ class CustomUserResource(resources.ModelResource):
|
|
71
86
|
'is_staff',
|
72
87
|
'is_superuser',
|
73
88
|
'groups',
|
89
|
+
'registration_sources',
|
90
|
+
'activities_count',
|
74
91
|
'last_login',
|
75
92
|
'date_joined',
|
76
93
|
)
|
@@ -79,11 +96,28 @@ class CustomUserResource(resources.ModelResource):
|
|
79
96
|
skip_unchanged = True
|
80
97
|
report_skipped = True
|
81
98
|
|
99
|
+
def dehydrate_registration_sources(self, user):
|
100
|
+
"""Get registration sources for export."""
|
101
|
+
sources = user.user_registration_sources.select_related('source').all()
|
102
|
+
return '|'.join([source.source.name for source in sources])
|
103
|
+
|
104
|
+
def dehydrate_activities_count(self, user):
|
105
|
+
"""Get activities count for export."""
|
106
|
+
return user.activities.count()
|
107
|
+
|
82
108
|
def before_import_row(self, row, **kwargs):
|
83
|
-
"""Process row before import."""
|
84
|
-
# Ensure email is lowercase
|
109
|
+
"""Process row before import with enhanced validation."""
|
110
|
+
# Ensure email is lowercase and valid
|
85
111
|
if 'email' in row:
|
86
|
-
|
112
|
+
email = row['email'].lower().strip()
|
113
|
+
if '@' not in email:
|
114
|
+
raise ValueError(f"Invalid email format: {email}")
|
115
|
+
row['email'] = email
|
116
|
+
|
117
|
+
# Clean phone number
|
118
|
+
if 'phone' in row and row['phone']:
|
119
|
+
phone = ''.join(filter(str.isdigit, str(row['phone'])))
|
120
|
+
row['phone'] = phone if phone else None
|
87
121
|
|
88
122
|
def skip_row(self, instance, original, row, import_validation_errors=None):
|
89
123
|
"""Skip rows with validation errors."""
|
@@ -93,7 +127,7 @@ class CustomUserResource(resources.ModelResource):
|
|
93
127
|
|
94
128
|
|
95
129
|
class UserActivityResource(resources.ModelResource):
|
96
|
-
"""
|
130
|
+
"""Enhanced resource for exporting user activity (export only)."""
|
97
131
|
|
98
132
|
user_email = fields.Field(
|
99
133
|
column_name='user_email',
|
@@ -118,6 +152,12 @@ class UserActivityResource(resources.ModelResource):
|
|
118
152
|
attribute='created_at',
|
119
153
|
widget=DateTimeWidget(format='%Y-%m-%d %H:%M:%S')
|
120
154
|
)
|
155
|
+
|
156
|
+
# Additional context fields
|
157
|
+
user_agent_browser = fields.Field(
|
158
|
+
column_name='user_agent_browser',
|
159
|
+
readonly=True
|
160
|
+
)
|
121
161
|
|
122
162
|
class Meta:
|
123
163
|
model = UserActivity
|
@@ -130,12 +170,30 @@ class UserActivityResource(resources.ModelResource):
|
|
130
170
|
'description',
|
131
171
|
'ip_address',
|
132
172
|
'user_agent',
|
173
|
+
'user_agent_browser',
|
133
174
|
'object_id',
|
134
175
|
'object_type',
|
135
176
|
'created_at',
|
136
177
|
)
|
137
178
|
export_order = fields
|
138
179
|
# No import - this is export only
|
180
|
+
|
181
|
+
def dehydrate_user_agent_browser(self, activity):
|
182
|
+
"""Extract browser info from user agent."""
|
183
|
+
if not activity.user_agent:
|
184
|
+
return "Unknown"
|
185
|
+
|
186
|
+
user_agent = activity.user_agent.lower()
|
187
|
+
if 'chrome' in user_agent:
|
188
|
+
return "Chrome"
|
189
|
+
elif 'firefox' in user_agent:
|
190
|
+
return "Firefox"
|
191
|
+
elif 'safari' in user_agent:
|
192
|
+
return "Safari"
|
193
|
+
elif 'edge' in user_agent:
|
194
|
+
return "Edge"
|
195
|
+
else:
|
196
|
+
return "Other"
|
139
197
|
|
140
198
|
def get_queryset(self):
|
141
199
|
"""Optimize queryset for export."""
|
@@ -143,7 +201,7 @@ class UserActivityResource(resources.ModelResource):
|
|
143
201
|
|
144
202
|
|
145
203
|
class RegistrationSourceResource(resources.ModelResource):
|
146
|
-
"""
|
204
|
+
"""Enhanced resource for importing/exporting registration sources."""
|
147
205
|
|
148
206
|
is_active = fields.Field(
|
149
207
|
column_name='is_active',
|
@@ -167,45 +225,56 @@ class RegistrationSourceResource(resources.ModelResource):
|
|
167
225
|
column_name='users_count',
|
168
226
|
readonly=True
|
169
227
|
)
|
228
|
+
|
229
|
+
recent_registrations = fields.Field(
|
230
|
+
column_name='recent_registrations_7d',
|
231
|
+
readonly=True
|
232
|
+
)
|
170
233
|
|
171
234
|
class Meta:
|
172
235
|
model = RegistrationSource
|
173
236
|
fields = (
|
174
237
|
'id',
|
175
|
-
'url',
|
176
238
|
'name',
|
177
239
|
'description',
|
178
240
|
'is_active',
|
179
241
|
'users_count',
|
242
|
+
'recent_registrations',
|
180
243
|
'created_at',
|
181
244
|
'updated_at',
|
182
245
|
)
|
183
246
|
export_order = fields
|
184
|
-
import_id_fields = ('
|
247
|
+
import_id_fields = ('name',) # Use name as unique identifier
|
185
248
|
skip_unchanged = True
|
186
249
|
report_skipped = True
|
187
250
|
|
188
251
|
def dehydrate_users_count(self, registration_source):
|
189
|
-
"""Calculate users count for export."""
|
252
|
+
"""Calculate total users count for export."""
|
190
253
|
return registration_source.user_registration_sources.count()
|
254
|
+
|
255
|
+
def dehydrate_recent_registrations(self, registration_source):
|
256
|
+
"""Calculate recent registrations count."""
|
257
|
+
|
258
|
+
week_ago = timezone.now() - timedelta(days=7)
|
259
|
+
return registration_source.user_registration_sources.filter(
|
260
|
+
created_at__gte=week_ago
|
261
|
+
).count()
|
191
262
|
|
192
263
|
def before_import_row(self, row, **kwargs):
|
193
|
-
"""Process row before import."""
|
194
|
-
#
|
195
|
-
if '
|
196
|
-
|
197
|
-
if
|
198
|
-
row['
|
199
|
-
else:
|
200
|
-
row['url'] = url
|
264
|
+
"""Process row before import with validation."""
|
265
|
+
# Clean and validate name
|
266
|
+
if 'name' in row and row['name']:
|
267
|
+
row['name'] = row['name'].strip()
|
268
|
+
if len(row['name']) < 2:
|
269
|
+
raise ValueError(f"Registration source name too short: {row['name']}")
|
201
270
|
|
202
271
|
|
203
272
|
class TwilioResponseResource(resources.ModelResource):
|
204
|
-
"""
|
273
|
+
"""Enhanced resource for exporting Twilio responses (export only)."""
|
205
274
|
|
206
275
|
otp_recipient = fields.Field(
|
207
276
|
column_name='otp_recipient',
|
208
|
-
attribute='
|
277
|
+
attribute='otp_secret__email',
|
209
278
|
readonly=True
|
210
279
|
)
|
211
280
|
|
@@ -240,6 +309,17 @@ class TwilioResponseResource(resources.ModelResource):
|
|
240
309
|
widget=BooleanWidget(),
|
241
310
|
readonly=True
|
242
311
|
)
|
312
|
+
|
313
|
+
# Additional computed fields
|
314
|
+
response_time_ms = fields.Field(
|
315
|
+
column_name='response_time_ms',
|
316
|
+
readonly=True
|
317
|
+
)
|
318
|
+
|
319
|
+
masked_recipient = fields.Field(
|
320
|
+
column_name='masked_recipient',
|
321
|
+
readonly=True
|
322
|
+
)
|
243
323
|
|
244
324
|
class Meta:
|
245
325
|
model = TwilioResponse
|
@@ -251,12 +331,14 @@ class TwilioResponseResource(resources.ModelResource):
|
|
251
331
|
'message_sid',
|
252
332
|
'verification_sid',
|
253
333
|
'to_number',
|
334
|
+
'masked_recipient',
|
254
335
|
'from_number',
|
255
336
|
'otp_recipient',
|
256
337
|
'error_code',
|
257
338
|
'error_message',
|
258
339
|
'price',
|
259
340
|
'price_unit',
|
341
|
+
'response_time_ms',
|
260
342
|
'has_error',
|
261
343
|
'is_successful',
|
262
344
|
'created_at',
|
@@ -265,6 +347,28 @@ class TwilioResponseResource(resources.ModelResource):
|
|
265
347
|
)
|
266
348
|
export_order = fields
|
267
349
|
# No import - this is export only
|
350
|
+
|
351
|
+
def dehydrate_response_time_ms(self, twilio_response):
|
352
|
+
"""Calculate response time if available."""
|
353
|
+
if twilio_response.twilio_created_at and twilio_response.created_at:
|
354
|
+
delta = twilio_response.twilio_created_at - twilio_response.created_at
|
355
|
+
return int(delta.total_seconds() * 1000)
|
356
|
+
return None
|
357
|
+
|
358
|
+
def dehydrate_masked_recipient(self, twilio_response):
|
359
|
+
"""Mask recipient for privacy."""
|
360
|
+
if not twilio_response.to_number:
|
361
|
+
return "—"
|
362
|
+
|
363
|
+
recipient = twilio_response.to_number
|
364
|
+
if '@' in recipient:
|
365
|
+
# Email masking
|
366
|
+
local, domain = recipient.split('@', 1)
|
367
|
+
masked_local = local[:2] + '*' * (len(local) - 2) if len(local) > 2 else local
|
368
|
+
return f"{masked_local}@{domain}"
|
369
|
+
else:
|
370
|
+
# Phone masking
|
371
|
+
return f"***{recipient[-4:]}" if len(recipient) > 4 else "***"
|
268
372
|
|
269
373
|
def get_queryset(self):
|
270
374
|
"""Optimize queryset for export."""
|