django-cfg 1.3.7__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/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.7.dist-info → django_cfg-1.3.9.dist-info}/METADATA +2 -1
- {django_cfg-1.3.7.dist-info → django_cfg-1.3.9.dist-info}/RECORD +223 -117
- 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.7.dist-info → django_cfg-1.3.9.dist-info}/WHEEL +0 -0
- {django_cfg-1.3.7.dist-info → django_cfg-1.3.9.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.3.7.dist-info → django_cfg-1.3.9.dist-info}/licenses/LICENSE +0 -0
@@ -1,46 +1,54 @@
|
|
1
1
|
"""
|
2
|
-
CloudflareApiKey admin
|
2
|
+
CloudflareApiKey admin using Django Admin Utilities.
|
3
3
|
|
4
|
-
|
4
|
+
Enhanced API key management with Material Icons and optimized queries.
|
5
5
|
"""
|
6
6
|
|
7
|
-
from django.contrib import admin
|
8
|
-
from django.utils.html import format_html
|
7
|
+
from django.contrib import admin, messages
|
9
8
|
from django.http import HttpRequest
|
9
|
+
from django.db import models
|
10
|
+
from django.db.models import Count
|
10
11
|
from typing import Any
|
11
|
-
|
12
12
|
from unfold.admin import ModelAdmin
|
13
|
-
|
13
|
+
|
14
|
+
from django_cfg.modules.django_admin import (
|
15
|
+
OptimizedModelAdmin,
|
16
|
+
DisplayMixin,
|
17
|
+
StatusBadgeConfig,
|
18
|
+
DateTimeDisplayConfig,
|
19
|
+
Icons,
|
20
|
+
ActionVariant,
|
21
|
+
display,
|
22
|
+
action
|
23
|
+
)
|
24
|
+
from django_cfg.modules.django_admin.utils.badges import StatusBadge
|
14
25
|
|
15
26
|
from ..models import CloudflareApiKey
|
16
27
|
|
17
28
|
|
18
29
|
@admin.register(CloudflareApiKey)
|
19
|
-
class CloudflareApiKeyAdmin(ModelAdmin):
|
20
|
-
"""Admin interface for CloudflareApiKey model."""
|
30
|
+
class CloudflareApiKeyAdmin(OptimizedModelAdmin, DisplayMixin, ModelAdmin):
|
31
|
+
"""Admin interface for CloudflareApiKey model using Django Admin Utilities."""
|
21
32
|
|
22
33
|
list_display = [
|
23
34
|
'status_display',
|
24
|
-
'
|
35
|
+
'name_display',
|
25
36
|
'description_preview',
|
26
|
-
'
|
27
|
-
'
|
37
|
+
'active_display',
|
38
|
+
'default_display',
|
28
39
|
'sites_count',
|
29
|
-
'
|
30
|
-
'
|
40
|
+
'last_used_display',
|
41
|
+
'created_at_display'
|
31
42
|
]
|
32
|
-
|
33
|
-
|
34
|
-
|
43
|
+
list_display_links = ['name_display']
|
44
|
+
ordering = ['-created_at']
|
35
45
|
search_fields = ['name', 'description', 'account_id']
|
36
|
-
|
37
46
|
list_filter = [
|
38
47
|
'is_active',
|
39
48
|
'is_default',
|
40
49
|
'created_at',
|
41
50
|
'last_used_at'
|
42
51
|
]
|
43
|
-
|
44
52
|
readonly_fields = [
|
45
53
|
'created_at',
|
46
54
|
'updated_at',
|
@@ -49,23 +57,25 @@ class CloudflareApiKeyAdmin(ModelAdmin):
|
|
49
57
|
]
|
50
58
|
|
51
59
|
fieldsets = [
|
52
|
-
('Basic Information', {
|
53
|
-
'fields': ['name', 'description']
|
60
|
+
('🔑 Basic Information', {
|
61
|
+
'fields': ['name', 'description'],
|
62
|
+
'classes': ('tab',)
|
54
63
|
}),
|
55
|
-
('Cloudflare Configuration', {
|
64
|
+
('☁️ Cloudflare Configuration', {
|
56
65
|
'fields': ['api_token', 'account_id'],
|
57
|
-
'classes':
|
66
|
+
'classes': ('tab', 'collapse')
|
58
67
|
}),
|
59
|
-
('Settings', {
|
60
|
-
'fields': ['is_active', 'is_default']
|
68
|
+
('⚙️ Settings', {
|
69
|
+
'fields': ['is_active', 'is_default'],
|
70
|
+
'classes': ('tab',)
|
61
71
|
}),
|
62
|
-
('Timestamps', {
|
72
|
+
('⏰ Timestamps', {
|
63
73
|
'fields': ['created_at', 'updated_at', 'last_used_at'],
|
64
|
-
'classes':
|
74
|
+
'classes': ('tab', 'collapse')
|
65
75
|
}),
|
66
|
-
('Usage', {
|
76
|
+
('📊 Usage', {
|
67
77
|
'fields': ['sites_using_key'],
|
68
|
-
'classes':
|
78
|
+
'classes': ('tab', 'collapse')
|
69
79
|
})
|
70
80
|
]
|
71
81
|
|
@@ -75,24 +85,47 @@ class CloudflareApiKeyAdmin(ModelAdmin):
|
|
75
85
|
'deactivate_keys_action'
|
76
86
|
]
|
77
87
|
|
78
|
-
# Display methods
|
79
|
-
|
80
88
|
@display(description="Status")
|
81
89
|
def status_display(self, obj: CloudflareApiKey) -> str:
|
82
|
-
"""Display status with
|
90
|
+
"""Display status with badge."""
|
83
91
|
if obj.is_active:
|
84
92
|
if obj.is_default:
|
85
|
-
|
93
|
+
config = StatusBadgeConfig(show_icons=True, icon=Icons.STAR)
|
94
|
+
return StatusBadge.create(
|
95
|
+
text=f"{obj.name} (Default)",
|
96
|
+
variant="success",
|
97
|
+
config=config
|
98
|
+
)
|
86
99
|
else:
|
87
|
-
|
100
|
+
config = StatusBadgeConfig(show_icons=True, icon=Icons.KEY)
|
101
|
+
return StatusBadge.create(
|
102
|
+
text=obj.name,
|
103
|
+
variant="success",
|
104
|
+
config=config
|
105
|
+
)
|
88
106
|
else:
|
89
|
-
|
107
|
+
config = StatusBadgeConfig(show_icons=True, icon=Icons.LOCK)
|
108
|
+
return StatusBadge.create(
|
109
|
+
text=obj.name,
|
110
|
+
variant="danger",
|
111
|
+
config=config
|
112
|
+
)
|
113
|
+
|
114
|
+
@display(description="Name", ordering="name")
|
115
|
+
def name_display(self, obj: CloudflareApiKey) -> str:
|
116
|
+
"""Display API key name."""
|
117
|
+
config = StatusBadgeConfig(show_icons=True, icon=Icons.KEY)
|
118
|
+
return StatusBadge.create(
|
119
|
+
text=obj.name,
|
120
|
+
variant="primary",
|
121
|
+
config=config
|
122
|
+
)
|
90
123
|
|
91
124
|
@display(description="Description")
|
92
125
|
def description_preview(self, obj: CloudflareApiKey) -> str:
|
93
126
|
"""Show description preview."""
|
94
127
|
if not obj.description:
|
95
|
-
return "
|
128
|
+
return "—"
|
96
129
|
|
97
130
|
preview = obj.description[:50]
|
98
131
|
if len(obj.description) > 50:
|
@@ -101,20 +134,23 @@ class CloudflareApiKeyAdmin(ModelAdmin):
|
|
101
134
|
return preview
|
102
135
|
|
103
136
|
@display(description="Active")
|
104
|
-
def
|
137
|
+
def active_display(self, obj: CloudflareApiKey) -> str:
|
105
138
|
"""Display active status badge."""
|
106
139
|
if obj.is_active:
|
107
|
-
|
140
|
+
config = StatusBadgeConfig(show_icons=True, icon=Icons.CHECK_CIRCLE)
|
141
|
+
return StatusBadge.create(text="Active", variant="success", config=config)
|
108
142
|
else:
|
109
|
-
|
143
|
+
config = StatusBadgeConfig(show_icons=True, icon=Icons.CANCEL)
|
144
|
+
return StatusBadge.create(text="Inactive", variant="secondary", config=config)
|
110
145
|
|
111
146
|
@display(description="Default")
|
112
|
-
def
|
147
|
+
def default_display(self, obj: CloudflareApiKey) -> str:
|
113
148
|
"""Display default status badge."""
|
114
149
|
if obj.is_default:
|
115
|
-
|
150
|
+
config = StatusBadgeConfig(show_icons=True, icon=Icons.STAR)
|
151
|
+
return StatusBadge.create(text="Default", variant="primary", config=config)
|
116
152
|
else:
|
117
|
-
return "
|
153
|
+
return "—"
|
118
154
|
|
119
155
|
@display(description="Sites")
|
120
156
|
def sites_count(self, obj: CloudflareApiKey) -> str:
|
@@ -124,62 +160,88 @@ class CloudflareApiKeyAdmin(ModelAdmin):
|
|
124
160
|
return f"{count} sites"
|
125
161
|
return "No sites"
|
126
162
|
|
163
|
+
@display(description="Last Used")
|
164
|
+
def last_used_display(self, obj: CloudflareApiKey) -> str:
|
165
|
+
"""Display last used time."""
|
166
|
+
if not obj.last_used_at:
|
167
|
+
return "Never"
|
168
|
+
config = DateTimeDisplayConfig(show_relative=True)
|
169
|
+
return self.display_datetime_relative(obj, 'last_used_at', config)
|
170
|
+
|
171
|
+
@display(description="Created")
|
172
|
+
def created_at_display(self, obj: CloudflareApiKey) -> str:
|
173
|
+
"""Created time with relative display."""
|
174
|
+
config = DateTimeDisplayConfig(show_relative=True)
|
175
|
+
return self.display_datetime_relative(obj, 'created_at', config)
|
176
|
+
|
127
177
|
def sites_using_key(self, obj: CloudflareApiKey) -> str:
|
128
178
|
"""Show sites using this API key."""
|
129
|
-
sites = obj.cloudflaresite_set.all()[:10]
|
179
|
+
sites = obj.cloudflaresite_set.all()[:10]
|
130
180
|
|
131
181
|
if not sites:
|
132
182
|
return "No sites using this key"
|
133
183
|
|
134
|
-
|
184
|
+
site_list = []
|
135
185
|
for site in sites:
|
136
186
|
status_emoji = "🔧" if site.maintenance_active else "🟢"
|
137
|
-
|
187
|
+
site_list.append(f"{status_emoji} {site.name} ({site.domain})")
|
138
188
|
|
139
|
-
|
189
|
+
result = "\n".join(site_list)
|
140
190
|
|
141
191
|
total_count = obj.cloudflaresite_set.count()
|
142
192
|
if total_count > 10:
|
143
|
-
|
193
|
+
result += f"\n... and {total_count - 10} more sites"
|
144
194
|
|
145
|
-
return
|
146
|
-
|
147
|
-
sites_using_key.short_description = "Sites Using This Key"
|
148
|
-
|
149
|
-
# Admin Actions
|
195
|
+
return result
|
150
196
|
|
151
|
-
@action(description="
|
197
|
+
@action(description="Make default API key", variant=ActionVariant.PRIMARY)
|
152
198
|
def make_default_action(self, request: HttpRequest, queryset) -> None:
|
153
199
|
"""Make selected key the default."""
|
154
200
|
if queryset.count() > 1:
|
155
|
-
|
201
|
+
messages.error(request, "Please select only one API key to make default.")
|
156
202
|
return
|
157
203
|
|
158
204
|
key = queryset.first()
|
159
205
|
if key:
|
160
|
-
# This will automatically set others to non-default via the model's save method
|
161
206
|
key.is_default = True
|
162
207
|
key.save()
|
163
|
-
|
208
|
+
messages.success(request, f"'{key.name}' is now the default API key.")
|
164
209
|
|
165
|
-
@action(description="
|
210
|
+
@action(description="Activate API keys", variant=ActionVariant.SUCCESS)
|
166
211
|
def activate_keys_action(self, request: HttpRequest, queryset) -> None:
|
167
212
|
"""Activate selected API keys."""
|
168
213
|
count = queryset.update(is_active=True)
|
169
|
-
|
214
|
+
messages.success(request, f"Successfully activated {count} API keys.")
|
170
215
|
|
171
|
-
@action(description="
|
216
|
+
@action(description="Deactivate API keys", variant=ActionVariant.DANGER)
|
172
217
|
def deactivate_keys_action(self, request: HttpRequest, queryset) -> None:
|
173
218
|
"""Deactivate selected API keys."""
|
174
|
-
# Don't allow deactivating the default key
|
175
219
|
default_keys = queryset.filter(is_default=True)
|
176
220
|
if default_keys.exists():
|
177
|
-
|
221
|
+
messages.error(
|
178
222
|
request,
|
179
|
-
"Cannot deactivate default API key. Please set another key as default first."
|
180
|
-
level='error'
|
223
|
+
"Cannot deactivate default API key. Please set another key as default first."
|
181
224
|
)
|
182
225
|
return
|
183
226
|
|
184
227
|
count = queryset.update(is_active=False)
|
185
|
-
|
228
|
+
messages.warning(request, f"Successfully deactivated {count} API keys.")
|
229
|
+
|
230
|
+
def changelist_view(self, request, extra_context=None):
|
231
|
+
"""Add API key statistics to changelist."""
|
232
|
+
extra_context = extra_context or {}
|
233
|
+
|
234
|
+
queryset = self.get_queryset(request)
|
235
|
+
stats = queryset.aggregate(
|
236
|
+
total_keys=Count('id'),
|
237
|
+
active_keys=Count('id', filter=models.Q(is_active=True)),
|
238
|
+
default_keys=Count('id', filter=models.Q(is_default=True))
|
239
|
+
)
|
240
|
+
|
241
|
+
extra_context['api_key_stats'] = {
|
242
|
+
'total_keys': stats['total_keys'] or 0,
|
243
|
+
'active_keys': stats['active_keys'] or 0,
|
244
|
+
'default_keys': stats['default_keys'] or 0
|
245
|
+
}
|
246
|
+
|
247
|
+
return super().changelist_view(request, extra_context)
|
@@ -1,48 +1,59 @@
|
|
1
1
|
"""
|
2
|
-
MaintenanceLog admin
|
2
|
+
MaintenanceLog admin using Django Admin Utilities.
|
3
3
|
|
4
4
|
Read-only admin interface for viewing maintenance operation logs.
|
5
5
|
"""
|
6
6
|
|
7
7
|
from django.contrib import admin
|
8
|
-
from django.utils.html import format_html
|
9
8
|
from django.http import HttpRequest
|
9
|
+
from django.db import models
|
10
|
+
from django.db.models import Count, Q
|
10
11
|
from typing import Any
|
11
12
|
import json
|
12
|
-
|
13
13
|
from unfold.admin import ModelAdmin
|
14
|
-
|
14
|
+
|
15
|
+
from django_cfg.modules.django_admin import (
|
16
|
+
OptimizedModelAdmin,
|
17
|
+
DisplayMixin,
|
18
|
+
StatusBadgeConfig,
|
19
|
+
DateTimeDisplayConfig,
|
20
|
+
Icons,
|
21
|
+
display
|
22
|
+
)
|
23
|
+
from django_cfg.modules.django_admin.utils.badges import StatusBadge
|
15
24
|
|
16
25
|
from ..models import MaintenanceLog
|
17
26
|
|
18
27
|
|
19
28
|
@admin.register(MaintenanceLog)
|
20
|
-
class MaintenanceLogAdmin(ModelAdmin):
|
21
|
-
"""Admin interface for MaintenanceLog model."""
|
29
|
+
class MaintenanceLogAdmin(OptimizedModelAdmin, DisplayMixin, ModelAdmin):
|
30
|
+
"""Admin interface for MaintenanceLog model using Django Admin Utilities."""
|
31
|
+
|
32
|
+
# Performance optimization
|
33
|
+
select_related_fields = ['site']
|
22
34
|
|
23
35
|
list_display = [
|
24
36
|
'status_display',
|
25
|
-
'
|
37
|
+
'site_display',
|
26
38
|
'action_display',
|
27
|
-
'
|
39
|
+
'created_at_display',
|
28
40
|
'duration_display',
|
29
41
|
'error_preview'
|
30
42
|
]
|
31
|
-
|
43
|
+
list_display_links = ['site_display']
|
44
|
+
ordering = ['-created_at']
|
32
45
|
list_filter = [
|
33
46
|
'action',
|
34
47
|
'status',
|
35
48
|
'created_at',
|
36
49
|
'site'
|
37
50
|
]
|
38
|
-
|
39
51
|
search_fields = [
|
40
52
|
'site__name',
|
41
53
|
'site__domain',
|
42
54
|
'reason',
|
43
55
|
'error_message'
|
44
56
|
]
|
45
|
-
|
46
57
|
readonly_fields = [
|
47
58
|
'site',
|
48
59
|
'action',
|
@@ -56,101 +67,172 @@ class MaintenanceLogAdmin(ModelAdmin):
|
|
56
67
|
]
|
57
68
|
|
58
69
|
fieldsets = [
|
59
|
-
('
|
60
|
-
'fields': ['site', 'action', 'status', '
|
70
|
+
('📋 Log Information', {
|
71
|
+
'fields': ['site', 'action', 'status', 'reason'],
|
72
|
+
'classes': ('tab',)
|
73
|
+
}),
|
74
|
+
('⏱️ Timing', {
|
75
|
+
'fields': ['created_at', 'duration_seconds'],
|
76
|
+
'classes': ('tab',)
|
61
77
|
}),
|
62
|
-
('
|
63
|
-
'fields': ['
|
78
|
+
('❌ Error Details', {
|
79
|
+
'fields': ['error_message'],
|
80
|
+
'classes': ('tab', 'collapse')
|
64
81
|
}),
|
65
|
-
('Cloudflare Response', {
|
82
|
+
('📊 Cloudflare Response', {
|
66
83
|
'fields': ['cloudflare_response_formatted'],
|
67
|
-
'classes':
|
84
|
+
'classes': ('tab', 'collapse')
|
68
85
|
})
|
69
86
|
]
|
70
87
|
|
71
|
-
|
72
|
-
|
73
|
-
def has_add_permission(self, request: HttpRequest) -> bool:
|
74
|
-
"""Logs are created automatically, no manual adding."""
|
88
|
+
def has_add_permission(self, request):
|
89
|
+
"""Disable adding new logs through admin."""
|
75
90
|
return False
|
76
91
|
|
77
|
-
def has_change_permission(self, request
|
78
|
-
"""
|
92
|
+
def has_change_permission(self, request, obj=None):
|
93
|
+
"""Disable editing logs through admin."""
|
79
94
|
return False
|
80
95
|
|
81
|
-
def has_delete_permission(self, request
|
82
|
-
"""Allow
|
96
|
+
def has_delete_permission(self, request, obj=None):
|
97
|
+
"""Allow deleting old logs."""
|
83
98
|
return True
|
84
99
|
|
85
|
-
|
86
|
-
|
87
|
-
@display(description="Status", ordering="status")
|
100
|
+
@display(description="Status")
|
88
101
|
def status_display(self, obj: MaintenanceLog) -> str:
|
89
|
-
"""Display status with
|
90
|
-
|
91
|
-
MaintenanceLog.Status.SUCCESS:
|
92
|
-
MaintenanceLog.Status.FAILED:
|
93
|
-
MaintenanceLog.Status.PENDING:
|
94
|
-
}
|
102
|
+
"""Display status with badge."""
|
103
|
+
status_variants = {
|
104
|
+
MaintenanceLog.Status.SUCCESS: 'success',
|
105
|
+
MaintenanceLog.Status.FAILED: 'danger',
|
106
|
+
MaintenanceLog.Status.PENDING: 'warning'
|
107
|
+
}
|
108
|
+
variant = status_variants.get(obj.status, 'secondary')
|
109
|
+
|
110
|
+
status_icons = {
|
111
|
+
MaintenanceLog.Status.SUCCESS: Icons.CHECK_CIRCLE,
|
112
|
+
MaintenanceLog.Status.FAILED: Icons.CANCEL,
|
113
|
+
MaintenanceLog.Status.PENDING: Icons.SCHEDULE
|
114
|
+
}
|
115
|
+
icon = status_icons.get(obj.status, Icons.HELP)
|
95
116
|
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
117
|
+
config = StatusBadgeConfig(show_icons=True, icon=icon)
|
118
|
+
return StatusBadge.create(
|
119
|
+
text=obj.get_status_display(),
|
120
|
+
variant=variant,
|
121
|
+
config=config
|
122
|
+
)
|
123
|
+
|
124
|
+
@display(description="Site", ordering="site__name")
|
125
|
+
def site_display(self, obj: MaintenanceLog) -> str:
|
126
|
+
"""Display site name."""
|
127
|
+
if not obj.site:
|
128
|
+
return "—"
|
101
129
|
|
102
|
-
|
103
|
-
|
104
|
-
|
130
|
+
config = StatusBadgeConfig(show_icons=True, icon=Icons.LANGUAGE)
|
131
|
+
return StatusBadge.create(
|
132
|
+
text=f"{obj.site.name} ({obj.site.domain})",
|
133
|
+
variant="info",
|
134
|
+
config=config
|
105
135
|
)
|
106
136
|
|
107
|
-
@display(description="Action"
|
137
|
+
@display(description="Action")
|
108
138
|
def action_display(self, obj: MaintenanceLog) -> str:
|
109
|
-
"""Display action with
|
139
|
+
"""Display action with badge."""
|
140
|
+
action_variants = {
|
141
|
+
MaintenanceLog.Action.ENABLE: 'warning',
|
142
|
+
MaintenanceLog.Action.DISABLE: 'success',
|
143
|
+
MaintenanceLog.Action.CHECK_STATUS: 'info'
|
144
|
+
}
|
145
|
+
variant = action_variants.get(obj.action, 'secondary')
|
146
|
+
|
110
147
|
action_icons = {
|
111
|
-
MaintenanceLog.Action.ENABLE:
|
112
|
-
MaintenanceLog.Action.DISABLE:
|
113
|
-
MaintenanceLog.Action.
|
114
|
-
MaintenanceLog.Action.ERROR: "❌"
|
148
|
+
MaintenanceLog.Action.ENABLE: Icons.BUILD,
|
149
|
+
MaintenanceLog.Action.DISABLE: Icons.CHECK_CIRCLE,
|
150
|
+
MaintenanceLog.Action.CHECK_STATUS: Icons.VISIBILITY
|
115
151
|
}
|
152
|
+
icon = action_icons.get(obj.action, Icons.SETTINGS)
|
116
153
|
|
117
|
-
|
118
|
-
return
|
154
|
+
config = StatusBadgeConfig(show_icons=True, icon=icon)
|
155
|
+
return StatusBadge.create(
|
156
|
+
text=obj.get_action_display(),
|
157
|
+
variant=variant,
|
158
|
+
config=config
|
159
|
+
)
|
160
|
+
|
161
|
+
@display(description="Created")
|
162
|
+
def created_at_display(self, obj: MaintenanceLog) -> str:
|
163
|
+
"""Created time with relative display."""
|
164
|
+
config = DateTimeDisplayConfig(show_relative=True)
|
165
|
+
return self.display_datetime_relative(obj, 'created_at', config)
|
119
166
|
|
120
167
|
@display(description="Duration")
|
121
168
|
def duration_display(self, obj: MaintenanceLog) -> str:
|
122
169
|
"""Display operation duration."""
|
123
|
-
if
|
124
|
-
return "
|
170
|
+
if obj.duration_seconds is None:
|
171
|
+
return "—"
|
125
172
|
|
126
|
-
if obj.duration_seconds <
|
127
|
-
return f"{obj.duration_seconds}
|
173
|
+
if obj.duration_seconds < 1:
|
174
|
+
return f"{obj.duration_seconds * 1000:.0f}ms"
|
175
|
+
elif obj.duration_seconds < 60:
|
176
|
+
return f"{obj.duration_seconds:.1f}s"
|
128
177
|
else:
|
129
178
|
minutes = obj.duration_seconds // 60
|
130
179
|
seconds = obj.duration_seconds % 60
|
131
|
-
return f"{minutes}m {seconds}s"
|
180
|
+
return f"{minutes:.0f}m {seconds:.0f}s"
|
132
181
|
|
133
182
|
@display(description="Error")
|
134
183
|
def error_preview(self, obj: MaintenanceLog) -> str:
|
135
184
|
"""Show error message preview."""
|
136
185
|
if not obj.error_message:
|
137
|
-
return "
|
186
|
+
return "—"
|
138
187
|
|
139
188
|
preview = obj.error_message[:100]
|
140
189
|
if len(obj.error_message) > 100:
|
141
190
|
preview += "..."
|
142
191
|
|
143
|
-
return
|
192
|
+
return preview
|
144
193
|
|
145
194
|
def cloudflare_response_formatted(self, obj: MaintenanceLog) -> str:
|
146
|
-
"""Format
|
195
|
+
"""Format cloudflare response for display."""
|
147
196
|
if not obj.cloudflare_response:
|
148
197
|
return "No response data"
|
149
198
|
|
150
199
|
try:
|
151
|
-
|
152
|
-
|
153
|
-
|
200
|
+
if isinstance(obj.cloudflare_response, str):
|
201
|
+
data = json.loads(obj.cloudflare_response)
|
202
|
+
else:
|
203
|
+
data = obj.cloudflare_response
|
204
|
+
|
205
|
+
return json.dumps(data, indent=2, ensure_ascii=False)
|
206
|
+
except (json.JSONDecodeError, TypeError):
|
154
207
|
return str(obj.cloudflare_response)
|
155
208
|
|
156
209
|
cloudflare_response_formatted.short_description = "Cloudflare Response (Formatted)"
|
210
|
+
|
211
|
+
def changelist_view(self, request, extra_context=None):
|
212
|
+
"""Add log statistics to changelist."""
|
213
|
+
extra_context = extra_context or {}
|
214
|
+
|
215
|
+
queryset = self.get_queryset(request)
|
216
|
+
stats = queryset.aggregate(
|
217
|
+
total_logs=Count('id'),
|
218
|
+
success_logs=Count('id', filter=Q(status=MaintenanceLog.Status.SUCCESS)),
|
219
|
+
failed_logs=Count('id', filter=Q(status=MaintenanceLog.Status.FAILED)),
|
220
|
+
pending_logs=Count('id', filter=Q(status=MaintenanceLog.Status.PENDING))
|
221
|
+
)
|
222
|
+
|
223
|
+
# Action breakdown
|
224
|
+
action_counts = dict(
|
225
|
+
queryset.values_list('action').annotate(
|
226
|
+
count=Count('id')
|
227
|
+
)
|
228
|
+
)
|
229
|
+
|
230
|
+
extra_context['log_stats'] = {
|
231
|
+
'total_logs': stats['total_logs'] or 0,
|
232
|
+
'success_logs': stats['success_logs'] or 0,
|
233
|
+
'failed_logs': stats['failed_logs'] or 0,
|
234
|
+
'pending_logs': stats['pending_logs'] or 0,
|
235
|
+
'action_counts': action_counts
|
236
|
+
}
|
237
|
+
|
238
|
+
return super().changelist_view(request, extra_context)
|