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,41 +1,51 @@
|
|
1
1
|
"""
|
2
|
-
|
2
|
+
ScheduledMaintenance admin using Django Admin Utilities.
|
3
3
|
|
4
|
-
|
4
|
+
Enhanced scheduled maintenance 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, HttpResponse
|
10
9
|
from django.shortcuts import redirect
|
11
|
-
from django.contrib import messages
|
12
10
|
from django.utils import timezone
|
13
11
|
from django.urls import path, reverse
|
14
12
|
from django.template.response import TemplateResponse
|
13
|
+
from django.db import models
|
14
|
+
from django.db.models import Count, Q
|
15
15
|
from typing import Any
|
16
|
-
|
17
16
|
from unfold.admin import ModelAdmin
|
18
|
-
|
19
|
-
from
|
17
|
+
|
18
|
+
from django_cfg.modules.django_admin import (
|
19
|
+
OptimizedModelAdmin,
|
20
|
+
DisplayMixin,
|
21
|
+
StatusBadgeConfig,
|
22
|
+
DateTimeDisplayConfig,
|
23
|
+
Icons,
|
24
|
+
ActionVariant,
|
25
|
+
display,
|
26
|
+
action
|
27
|
+
)
|
28
|
+
from django_cfg.modules.django_admin.utils.badges import StatusBadge
|
20
29
|
|
21
30
|
from ..models import ScheduledMaintenance, CloudflareSite
|
22
31
|
|
23
32
|
|
24
33
|
@admin.register(ScheduledMaintenance)
|
25
|
-
class ScheduledMaintenanceAdmin(ModelAdmin):
|
26
|
-
"""Admin for ScheduledMaintenance
|
34
|
+
class ScheduledMaintenanceAdmin(OptimizedModelAdmin, DisplayMixin, ModelAdmin):
|
35
|
+
"""Admin for ScheduledMaintenance using Django Admin Utilities."""
|
27
36
|
|
28
37
|
list_display = [
|
29
38
|
"status_display",
|
30
|
-
"
|
31
|
-
"
|
39
|
+
"title_display",
|
40
|
+
"scheduled_start_display",
|
32
41
|
"duration_display",
|
33
42
|
"sites_count",
|
34
|
-
"
|
35
|
-
"
|
36
|
-
"
|
43
|
+
"priority_display",
|
44
|
+
"auto_flags_display",
|
45
|
+
"created_at_display",
|
37
46
|
]
|
38
|
-
list_display_links = ["
|
47
|
+
list_display_links = ["title_display"]
|
48
|
+
ordering = ["-scheduled_start"]
|
39
49
|
search_fields = ["title", "description", "maintenance_message"]
|
40
50
|
list_filter = [
|
41
51
|
"status",
|
@@ -45,346 +55,247 @@ class ScheduledMaintenanceAdmin(ModelAdmin):
|
|
45
55
|
"scheduled_start",
|
46
56
|
"created_at"
|
47
57
|
]
|
48
|
-
ordering = ["-scheduled_start"]
|
49
58
|
|
50
59
|
fieldsets = [
|
51
|
-
("Basic Information", {
|
52
|
-
|
60
|
+
("📋 Basic Information", {
|
61
|
+
"fields": ["title", "description"],
|
62
|
+
"classes": ("tab",)
|
53
63
|
}),
|
54
|
-
("Scheduling", {
|
55
|
-
|
64
|
+
("⏰ Scheduling", {
|
65
|
+
"fields": ["scheduled_start", "duration_minutes"],
|
66
|
+
"classes": ("tab",)
|
56
67
|
}),
|
57
|
-
("Sites", {
|
58
|
-
|
68
|
+
("🌐 Sites", {
|
69
|
+
"fields": ["sites"],
|
70
|
+
"classes": ("tab",)
|
59
71
|
}),
|
60
|
-
("
|
61
|
-
|
72
|
+
("⚙️ Settings", {
|
73
|
+
"fields": ["priority", "auto_enable", "auto_disable"],
|
74
|
+
"classes": ("tab",)
|
62
75
|
}),
|
63
|
-
("
|
64
|
-
|
65
|
-
|
76
|
+
("💬 Messages", {
|
77
|
+
"fields": ["maintenance_message"],
|
78
|
+
"classes": ("tab", "collapse")
|
66
79
|
}),
|
67
|
-
("
|
68
|
-
|
69
|
-
|
80
|
+
("📊 Status", {
|
81
|
+
"fields": ["status"],
|
82
|
+
"classes": ("tab",)
|
70
83
|
}),
|
71
|
-
("
|
72
|
-
|
73
|
-
|
84
|
+
("⏰ Timestamps", {
|
85
|
+
"fields": ["created_at", "updated_at"],
|
86
|
+
"classes": ("tab", "collapse")
|
74
87
|
}),
|
75
88
|
]
|
76
89
|
|
77
|
-
|
78
|
-
|
79
|
-
filter_horizontal = ['sites']
|
90
|
+
filter_horizontal = ["sites"]
|
80
91
|
|
81
92
|
actions = [
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
'duplicate_maintenance_action'
|
93
|
+
"execute_maintenance_action",
|
94
|
+
"cancel_maintenance_action",
|
95
|
+
"reschedule_maintenance_action"
|
86
96
|
]
|
87
97
|
|
88
|
-
def get_urls(self):
|
89
|
-
"""Add custom admin URLs."""
|
90
|
-
urls = super().get_urls()
|
91
|
-
custom_urls = [
|
92
|
-
path(
|
93
|
-
'calendar/',
|
94
|
-
self.admin_site.admin_view(self.calendar_view),
|
95
|
-
name='scheduled_maintenance_calendar'
|
96
|
-
),
|
97
|
-
path(
|
98
|
-
'<int:object_id>/start/',
|
99
|
-
self.admin_site.admin_view(self.start_maintenance_view),
|
100
|
-
name='scheduled_maintenance_start'
|
101
|
-
),
|
102
|
-
path(
|
103
|
-
'<int:object_id>/complete/',
|
104
|
-
self.admin_site.admin_view(self.complete_maintenance_view),
|
105
|
-
name='scheduled_maintenance_complete'
|
106
|
-
),
|
107
|
-
]
|
108
|
-
return custom_urls + urls
|
109
|
-
|
110
98
|
@display(description="Status")
|
111
99
|
def status_display(self, obj: ScheduledMaintenance) -> str:
|
112
|
-
"""Display status with
|
113
|
-
|
114
|
-
ScheduledMaintenance.Status.SCHEDULED:
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
ScheduledMaintenance.Status.ACTIVE: {
|
120
|
-
'emoji': '🔧',
|
121
|
-
'color': 'orange',
|
122
|
-
'text': 'Active'
|
123
|
-
},
|
124
|
-
ScheduledMaintenance.Status.COMPLETED: {
|
125
|
-
'emoji': '✅',
|
126
|
-
'color': 'green',
|
127
|
-
'text': 'Completed'
|
128
|
-
},
|
129
|
-
ScheduledMaintenance.Status.CANCELLED: {
|
130
|
-
'emoji': '❌',
|
131
|
-
'color': 'red',
|
132
|
-
'text': 'Cancelled'
|
133
|
-
},
|
134
|
-
ScheduledMaintenance.Status.FAILED: {
|
135
|
-
'emoji': '💥',
|
136
|
-
'color': 'red',
|
137
|
-
'text': 'Failed'
|
138
|
-
},
|
100
|
+
"""Display status with badge."""
|
101
|
+
status_variants = {
|
102
|
+
ScheduledMaintenance.Status.SCHEDULED: 'warning',
|
103
|
+
ScheduledMaintenance.Status.ACTIVE: 'info',
|
104
|
+
ScheduledMaintenance.Status.COMPLETED: 'success',
|
105
|
+
ScheduledMaintenance.Status.CANCELLED: 'secondary',
|
106
|
+
ScheduledMaintenance.Status.FAILED: 'danger'
|
139
107
|
}
|
108
|
+
variant = status_variants.get(obj.status, 'secondary')
|
140
109
|
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
if obj.status == ScheduledMaintenance.Status.SCHEDULED:
|
150
|
-
if obj.is_due:
|
151
|
-
timing_info = " <small>(Due now!)</small>"
|
152
|
-
elif obj.time_until_start:
|
153
|
-
hours = int(obj.time_until_start.total_seconds() // 3600)
|
154
|
-
if hours < 24:
|
155
|
-
timing_info = f" <small>(in {hours}h)</small>"
|
156
|
-
elif obj.status == ScheduledMaintenance.Status.ACTIVE:
|
157
|
-
if obj.is_overdue:
|
158
|
-
timing_info = " <small>(Overdue!)</small>"
|
159
|
-
elif obj.time_until_end:
|
160
|
-
hours = int(obj.time_until_end.total_seconds() // 3600)
|
161
|
-
minutes = int((obj.time_until_end.total_seconds() % 3600) // 60)
|
162
|
-
timing_info = f" <small>({hours}h {minutes}m left)</small>"
|
110
|
+
status_icons = {
|
111
|
+
ScheduledMaintenance.Status.SCHEDULED: Icons.SCHEDULE,
|
112
|
+
ScheduledMaintenance.Status.ACTIVE: Icons.PLAY_ARROW,
|
113
|
+
ScheduledMaintenance.Status.COMPLETED: Icons.CHECK_CIRCLE,
|
114
|
+
ScheduledMaintenance.Status.CANCELLED: Icons.CANCEL,
|
115
|
+
ScheduledMaintenance.Status.FAILED: Icons.ERROR
|
116
|
+
}
|
117
|
+
icon = status_icons.get(obj.status, Icons.HELP)
|
163
118
|
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
config
|
169
|
-
timing_info
|
119
|
+
config = StatusBadgeConfig(show_icons=True, icon=icon)
|
120
|
+
return StatusBadge.create(
|
121
|
+
text=obj.get_status_display(),
|
122
|
+
variant=variant,
|
123
|
+
config=config
|
170
124
|
)
|
171
125
|
|
126
|
+
@display(description="Title", ordering="title")
|
127
|
+
def title_display(self, obj: ScheduledMaintenance) -> str:
|
128
|
+
"""Display maintenance title."""
|
129
|
+
config = StatusBadgeConfig(show_icons=True, icon=Icons.EVENT)
|
130
|
+
return StatusBadge.create(
|
131
|
+
text=obj.title,
|
132
|
+
variant="primary",
|
133
|
+
config=config
|
134
|
+
)
|
135
|
+
|
136
|
+
@display(description="Scheduled Start")
|
137
|
+
def scheduled_start_display(self, obj: ScheduledMaintenance) -> str:
|
138
|
+
"""Display scheduled start time."""
|
139
|
+
config = DateTimeDisplayConfig(show_relative=True)
|
140
|
+
return self.display_datetime_relative(obj, 'scheduled_start', config)
|
141
|
+
|
172
142
|
@display(description="Duration")
|
173
143
|
def duration_display(self, obj: ScheduledMaintenance) -> str:
|
174
|
-
"""Display
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
return f"{estimated_hours:.1f}h"
|
144
|
+
"""Display maintenance duration."""
|
145
|
+
if obj.duration_minutes < 60:
|
146
|
+
return f"{obj.duration_minutes} min"
|
147
|
+
else:
|
148
|
+
hours = obj.duration_minutes // 60
|
149
|
+
minutes = obj.duration_minutes % 60
|
150
|
+
if minutes == 0:
|
151
|
+
return f"{hours}h"
|
152
|
+
else:
|
153
|
+
return f"{hours}h {minutes}m"
|
186
154
|
|
187
155
|
@display(description="Sites")
|
188
156
|
def sites_count(self, obj: ScheduledMaintenance) -> str:
|
189
|
-
"""Display
|
190
|
-
count = obj.
|
157
|
+
"""Display count of affected sites."""
|
158
|
+
count = obj.sites.count()
|
191
159
|
if count == 0:
|
192
|
-
return
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
count
|
197
|
-
)
|
160
|
+
return "No sites"
|
161
|
+
elif count == 1:
|
162
|
+
return "1 site"
|
163
|
+
else:
|
164
|
+
return f"{count} sites"
|
198
165
|
|
199
166
|
@display(description="Priority")
|
200
|
-
def
|
201
|
-
"""Display priority badge."""
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
167
|
+
def priority_display(self, obj: ScheduledMaintenance) -> str:
|
168
|
+
"""Display priority with badge."""
|
169
|
+
priority_variants = {
|
170
|
+
ScheduledMaintenance.Priority.LOW: 'secondary',
|
171
|
+
ScheduledMaintenance.Priority.MEDIUM: 'info',
|
172
|
+
ScheduledMaintenance.Priority.HIGH: 'warning',
|
173
|
+
ScheduledMaintenance.Priority.CRITICAL: 'danger'
|
207
174
|
}
|
175
|
+
variant = priority_variants.get(obj.priority, 'secondary')
|
208
176
|
|
209
|
-
|
177
|
+
priority_icons = {
|
178
|
+
ScheduledMaintenance.Priority.LOW: Icons.KEYBOARD_ARROW_DOWN,
|
179
|
+
ScheduledMaintenance.Priority.MEDIUM: Icons.REMOVE,
|
180
|
+
ScheduledMaintenance.Priority.HIGH: Icons.KEYBOARD_ARROW_UP,
|
181
|
+
ScheduledMaintenance.Priority.CRITICAL: Icons.PRIORITY_HIGH
|
182
|
+
}
|
183
|
+
icon = priority_icons.get(obj.priority, Icons.HELP)
|
210
184
|
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
185
|
+
config = StatusBadgeConfig(show_icons=True, icon=icon)
|
186
|
+
return StatusBadge.create(
|
187
|
+
text=obj.get_priority_display(),
|
188
|
+
variant=variant,
|
189
|
+
config=config
|
216
190
|
)
|
217
191
|
|
218
|
-
@display(description="Auto")
|
219
|
-
def
|
220
|
-
"""Display
|
192
|
+
@display(description="Auto Flags")
|
193
|
+
def auto_flags_display(self, obj: ScheduledMaintenance) -> str:
|
194
|
+
"""Display auto enable/disable flags."""
|
221
195
|
flags = []
|
222
|
-
|
223
196
|
if obj.auto_enable:
|
224
|
-
flags.append(
|
225
|
-
|
197
|
+
flags.append("Auto Enable")
|
226
198
|
if obj.auto_disable:
|
227
|
-
flags.append(
|
199
|
+
flags.append("Auto Disable")
|
228
200
|
|
229
201
|
if not flags:
|
230
|
-
return
|
202
|
+
return "Manual"
|
231
203
|
|
232
|
-
return
|
204
|
+
return " | ".join(flags)
|
233
205
|
|
234
|
-
@
|
235
|
-
def
|
236
|
-
"""
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
for maintenance in queryset:
|
241
|
-
if maintenance.status == ScheduledMaintenance.Status.SCHEDULED:
|
242
|
-
try:
|
243
|
-
result = maintenance.start_maintenance()
|
244
|
-
if result['success']:
|
245
|
-
started += 1
|
246
|
-
else:
|
247
|
-
failed += 1
|
248
|
-
except Exception as e:
|
249
|
-
failed += 1
|
250
|
-
messages.error(request, f"Failed to start {maintenance.title}: {e}")
|
251
|
-
|
252
|
-
if started > 0:
|
253
|
-
messages.success(request, f"Started {started} maintenance events")
|
254
|
-
if failed > 0:
|
255
|
-
messages.error(request, f"Failed to start {failed} maintenance events")
|
206
|
+
@display(description="Created")
|
207
|
+
def created_at_display(self, obj: ScheduledMaintenance) -> str:
|
208
|
+
"""Created time with relative display."""
|
209
|
+
config = DateTimeDisplayConfig(show_relative=True)
|
210
|
+
return self.display_datetime_relative(obj, 'created_at', config)
|
256
211
|
|
257
|
-
@action(description="
|
258
|
-
def
|
259
|
-
"""
|
260
|
-
|
261
|
-
|
212
|
+
@action(description="Execute maintenance", variant=ActionVariant.WARNING)
|
213
|
+
def execute_maintenance_action(self, request: HttpRequest, queryset) -> None:
|
214
|
+
"""Execute selected maintenance tasks."""
|
215
|
+
scheduled_count = queryset.filter(status=ScheduledMaintenance.Status.SCHEDULED).count()
|
216
|
+
if scheduled_count == 0:
|
217
|
+
messages.error(request, "No scheduled maintenance tasks selected.")
|
218
|
+
return
|
262
219
|
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
if result['success']:
|
268
|
-
completed += 1
|
269
|
-
else:
|
270
|
-
failed += 1
|
271
|
-
except Exception as e:
|
272
|
-
failed += 1
|
273
|
-
messages.error(request, f"Failed to complete {maintenance.title}: {e}")
|
220
|
+
# Update status to active
|
221
|
+
queryset.filter(status=ScheduledMaintenance.Status.SCHEDULED).update(
|
222
|
+
status=ScheduledMaintenance.Status.ACTIVE
|
223
|
+
)
|
274
224
|
|
275
|
-
|
276
|
-
messages.success(request, f"Completed {completed} maintenance events")
|
277
|
-
if failed > 0:
|
278
|
-
messages.error(request, f"Failed to complete {failed} maintenance events")
|
225
|
+
messages.success(request, f"Started execution of {scheduled_count} maintenance tasks.")
|
279
226
|
|
280
|
-
@action(description="Cancel
|
281
|
-
def cancel_maintenance_action(self, request: HttpRequest, queryset
|
282
|
-
"""Cancel selected maintenance
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
if maintenance.status in [ScheduledMaintenance.Status.SCHEDULED, ScheduledMaintenance.Status.ACTIVE]:
|
287
|
-
try:
|
288
|
-
result = maintenance.cancel_maintenance(reason="Cancelled via admin")
|
289
|
-
if result['success']:
|
290
|
-
cancelled += 1
|
291
|
-
except Exception as e:
|
292
|
-
messages.error(request, f"Failed to cancel {maintenance.title}: {e}")
|
227
|
+
@action(description="Cancel maintenance", variant=ActionVariant.DANGER)
|
228
|
+
def cancel_maintenance_action(self, request: HttpRequest, queryset) -> None:
|
229
|
+
"""Cancel selected maintenance tasks."""
|
230
|
+
cancelable_count = queryset.filter(
|
231
|
+
status__in=[ScheduledMaintenance.Status.SCHEDULED, ScheduledMaintenance.Status.ACTIVE]
|
232
|
+
).count()
|
293
233
|
|
294
|
-
if
|
295
|
-
messages.
|
296
|
-
|
297
|
-
@action(description="Duplicate Maintenance")
|
298
|
-
def duplicate_maintenance_action(self, request: HttpRequest, queryset: Any) -> None:
|
299
|
-
"""Duplicate selected maintenance events."""
|
300
|
-
duplicated = 0
|
234
|
+
if cancelable_count == 0:
|
235
|
+
messages.error(request, "No cancelable maintenance tasks selected.")
|
236
|
+
return
|
301
237
|
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
new_start = maintenance.scheduled_start + timezone.timedelta(weeks=1)
|
306
|
-
|
307
|
-
duplicate = ScheduledMaintenance.objects.create(
|
308
|
-
title=f"{maintenance.title} (Copy)",
|
309
|
-
description=maintenance.description,
|
310
|
-
scheduled_start=new_start,
|
311
|
-
estimated_duration=maintenance.estimated_duration,
|
312
|
-
maintenance_message=maintenance.maintenance_message,
|
313
|
-
template=maintenance.template,
|
314
|
-
priority=maintenance.priority,
|
315
|
-
auto_enable=maintenance.auto_enable,
|
316
|
-
auto_disable=maintenance.auto_disable,
|
317
|
-
notify_before=maintenance.notify_before,
|
318
|
-
created_by=f"{maintenance.created_by} (duplicate)"
|
319
|
-
)
|
320
|
-
|
321
|
-
# Copy sites
|
322
|
-
duplicate.sites.set(maintenance.sites.all())
|
323
|
-
duplicated += 1
|
324
|
-
|
325
|
-
except Exception as e:
|
326
|
-
messages.error(request, f"Failed to duplicate {maintenance.title}: {e}")
|
238
|
+
queryset.filter(
|
239
|
+
status__in=[ScheduledMaintenance.Status.SCHEDULED, ScheduledMaintenance.Status.ACTIVE]
|
240
|
+
).update(status=ScheduledMaintenance.Status.CANCELLED)
|
327
241
|
|
328
|
-
|
329
|
-
messages.success(request, f"Duplicated {duplicated} maintenance events")
|
242
|
+
messages.warning(request, f"Cancelled {cancelable_count} maintenance tasks.")
|
330
243
|
|
331
|
-
|
332
|
-
|
333
|
-
|
244
|
+
@action(description="Reschedule maintenance", variant=ActionVariant.INFO)
|
245
|
+
def reschedule_maintenance_action(self, request: HttpRequest, queryset) -> None:
|
246
|
+
"""Reschedule selected maintenance tasks."""
|
247
|
+
reschedulable_count = queryset.filter(
|
248
|
+
status__in=[ScheduledMaintenance.Status.CANCELLED, ScheduledMaintenance.Status.FAILED]
|
249
|
+
).count()
|
334
250
|
|
335
|
-
|
251
|
+
if reschedulable_count == 0:
|
252
|
+
messages.error(request, "No reschedulable maintenance tasks selected.")
|
253
|
+
return
|
336
254
|
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
}
|
255
|
+
# Reset to scheduled status
|
256
|
+
queryset.filter(
|
257
|
+
status__in=[ScheduledMaintenance.Status.CANCELLED, ScheduledMaintenance.Status.FAILED]
|
258
|
+
).update(status=ScheduledMaintenance.Status.SCHEDULED)
|
342
259
|
|
343
|
-
|
344
|
-
request,
|
345
|
-
'admin/maintenance/scheduled_maintenance_calendar.html',
|
346
|
-
context
|
347
|
-
)
|
260
|
+
messages.info(request, f"Reset {reschedulable_count} maintenance tasks to scheduled.")
|
348
261
|
|
349
|
-
def
|
350
|
-
"""
|
351
|
-
|
262
|
+
def changelist_view(self, request, extra_context=None):
|
263
|
+
"""Add maintenance statistics to changelist."""
|
264
|
+
extra_context = extra_context or {}
|
352
265
|
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
)
|
363
|
-
else:
|
364
|
-
messages.error(request, f"Failed to start maintenance: {result.get('error')}")
|
365
|
-
except Exception as e:
|
366
|
-
messages.error(request, f"Error starting maintenance: {e}")
|
266
|
+
queryset = self.get_queryset(request)
|
267
|
+
stats = queryset.aggregate(
|
268
|
+
total_maintenance=Count('id'),
|
269
|
+
scheduled_maintenance=Count('id', filter=Q(status=ScheduledMaintenance.Status.SCHEDULED)),
|
270
|
+
active_maintenance=Count('id', filter=Q(status=ScheduledMaintenance.Status.ACTIVE)),
|
271
|
+
completed_maintenance=Count('id', filter=Q(status=ScheduledMaintenance.Status.COMPLETED)),
|
272
|
+
failed_maintenance=Count('id', filter=Q(status=ScheduledMaintenance.Status.FAILED)),
|
273
|
+
cancelled_maintenance=Count('id', filter=Q(status=ScheduledMaintenance.Status.CANCELLED))
|
274
|
+
)
|
367
275
|
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
276
|
+
# Priority breakdown
|
277
|
+
priority_counts = dict(
|
278
|
+
queryset.values_list('priority').annotate(
|
279
|
+
count=Count('id')
|
280
|
+
)
|
281
|
+
)
|
373
282
|
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
283
|
+
# Upcoming maintenance (next 7 days)
|
284
|
+
upcoming_maintenance = queryset.filter(
|
285
|
+
scheduled_start__gte=timezone.now(),
|
286
|
+
scheduled_start__lte=timezone.now() + timezone.timedelta(days=7),
|
287
|
+
status=ScheduledMaintenance.Status.SCHEDULED
|
288
|
+
).count()
|
289
|
+
|
290
|
+
extra_context['maintenance_stats'] = {
|
291
|
+
'total_maintenance': stats['total_maintenance'] or 0,
|
292
|
+
'scheduled_maintenance': stats['scheduled_maintenance'] or 0,
|
293
|
+
'active_maintenance': stats['active_maintenance'] or 0,
|
294
|
+
'completed_maintenance': stats['completed_maintenance'] or 0,
|
295
|
+
'failed_maintenance': stats['failed_maintenance'] or 0,
|
296
|
+
'cancelled_maintenance': stats['cancelled_maintenance'] or 0,
|
297
|
+
'priority_counts': priority_counts,
|
298
|
+
'upcoming_maintenance': upcoming_maintenance
|
299
|
+
}
|
389
300
|
|
390
|
-
return
|
301
|
+
return super().changelist_view(request, extra_context)
|