django-cfg 1.2.16__py3-none-any.whl → 1.2.18__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.
Files changed (81) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/accounts/models/__init__.py +68 -0
  3. django_cfg/apps/accounts/models/activity.py +34 -0
  4. django_cfg/apps/accounts/models/auth.py +50 -0
  5. django_cfg/apps/accounts/models/base.py +8 -0
  6. django_cfg/apps/accounts/models/choices.py +32 -0
  7. django_cfg/apps/accounts/models/integrations.py +75 -0
  8. django_cfg/apps/accounts/models/registration.py +52 -0
  9. django_cfg/apps/accounts/models/user.py +80 -0
  10. django_cfg/apps/maintenance/__init__.py +53 -24
  11. django_cfg/apps/maintenance/admin/__init__.py +7 -18
  12. django_cfg/apps/maintenance/admin/api_key_admin.py +185 -0
  13. django_cfg/apps/maintenance/admin/log_admin.py +156 -0
  14. django_cfg/apps/maintenance/admin/scheduled_admin.py +390 -0
  15. django_cfg/apps/maintenance/admin/site_admin.py +448 -0
  16. django_cfg/apps/maintenance/apps.py +9 -96
  17. django_cfg/apps/maintenance/management/commands/maintenance.py +193 -307
  18. django_cfg/apps/maintenance/management/commands/process_scheduled_maintenance.py +241 -0
  19. django_cfg/apps/maintenance/management/commands/sync_cloudflare.py +152 -111
  20. django_cfg/apps/maintenance/managers/__init__.py +7 -12
  21. django_cfg/apps/maintenance/managers/cloudflare_site_manager.py +192 -0
  22. django_cfg/apps/maintenance/managers/maintenance_log_manager.py +151 -0
  23. django_cfg/apps/maintenance/migrations/0001_initial.py +145 -705
  24. django_cfg/apps/maintenance/migrations/0002_cloudflaresite_maintenance_url.py +21 -0
  25. django_cfg/apps/maintenance/models/__init__.py +23 -21
  26. django_cfg/apps/maintenance/models/cloudflare_api_key.py +109 -0
  27. django_cfg/apps/maintenance/models/cloudflare_site.py +125 -0
  28. django_cfg/apps/maintenance/models/maintenance_log.py +131 -0
  29. django_cfg/apps/maintenance/models/scheduled_maintenance.py +307 -0
  30. django_cfg/apps/maintenance/services/__init__.py +37 -16
  31. django_cfg/apps/maintenance/services/bulk_operations_service.py +400 -0
  32. django_cfg/apps/maintenance/services/maintenance_service.py +230 -0
  33. django_cfg/apps/maintenance/services/scheduled_maintenance_service.py +381 -0
  34. django_cfg/apps/maintenance/services/site_sync_service.py +390 -0
  35. django_cfg/apps/maintenance/utils/__init__.py +12 -0
  36. django_cfg/apps/maintenance/utils/retry_utils.py +109 -0
  37. django_cfg/config.py +3 -0
  38. django_cfg/core/config.py +4 -6
  39. django_cfg/modules/django_unfold/dashboard.py +4 -5
  40. {django_cfg-1.2.16.dist-info → django_cfg-1.2.18.dist-info}/METADATA +52 -1
  41. {django_cfg-1.2.16.dist-info → django_cfg-1.2.18.dist-info}/RECORD +45 -55
  42. django_cfg/apps/maintenance/README.md +0 -305
  43. django_cfg/apps/maintenance/admin/deployments_admin.py +0 -251
  44. django_cfg/apps/maintenance/admin/events_admin.py +0 -374
  45. django_cfg/apps/maintenance/admin/monitoring_admin.py +0 -215
  46. django_cfg/apps/maintenance/admin/sites_admin.py +0 -464
  47. django_cfg/apps/maintenance/managers/deployments.py +0 -287
  48. django_cfg/apps/maintenance/managers/events.py +0 -374
  49. django_cfg/apps/maintenance/managers/monitoring.py +0 -301
  50. django_cfg/apps/maintenance/managers/sites.py +0 -335
  51. django_cfg/apps/maintenance/models/cloudflare.py +0 -316
  52. django_cfg/apps/maintenance/models/maintenance.py +0 -334
  53. django_cfg/apps/maintenance/models/monitoring.py +0 -393
  54. django_cfg/apps/maintenance/models/sites.py +0 -419
  55. django_cfg/apps/maintenance/serializers/__init__.py +0 -60
  56. django_cfg/apps/maintenance/serializers/actions.py +0 -310
  57. django_cfg/apps/maintenance/serializers/base.py +0 -44
  58. django_cfg/apps/maintenance/serializers/deployments.py +0 -209
  59. django_cfg/apps/maintenance/serializers/events.py +0 -210
  60. django_cfg/apps/maintenance/serializers/monitoring.py +0 -278
  61. django_cfg/apps/maintenance/serializers/sites.py +0 -213
  62. django_cfg/apps/maintenance/services/README.md +0 -168
  63. django_cfg/apps/maintenance/services/cloudflare_client.py +0 -441
  64. django_cfg/apps/maintenance/services/dns_manager.py +0 -497
  65. django_cfg/apps/maintenance/services/maintenance_manager.py +0 -504
  66. django_cfg/apps/maintenance/services/site_sync.py +0 -448
  67. django_cfg/apps/maintenance/services/sync_command_service.py +0 -330
  68. django_cfg/apps/maintenance/services/worker_manager.py +0 -264
  69. django_cfg/apps/maintenance/signals.py +0 -38
  70. django_cfg/apps/maintenance/urls.py +0 -36
  71. django_cfg/apps/maintenance/views/__init__.py +0 -18
  72. django_cfg/apps/maintenance/views/base.py +0 -61
  73. django_cfg/apps/maintenance/views/deployments.py +0 -175
  74. django_cfg/apps/maintenance/views/events.py +0 -204
  75. django_cfg/apps/maintenance/views/monitoring.py +0 -213
  76. django_cfg/apps/maintenance/views/sites.py +0 -338
  77. django_cfg/models/cloudflare.py +0 -316
  78. /django_cfg/apps/accounts/{models.py → __models.py} +0 -0
  79. {django_cfg-1.2.16.dist-info → django_cfg-1.2.18.dist-info}/WHEEL +0 -0
  80. {django_cfg-1.2.16.dist-info → django_cfg-1.2.18.dist-info}/entry_points.txt +0 -0
  81. {django_cfg-1.2.16.dist-info → django_cfg-1.2.18.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,185 @@
1
+ """
2
+ CloudflareApiKey admin with Unfold styling.
3
+
4
+ Admin interface for managing Cloudflare API keys.
5
+ """
6
+
7
+ from django.contrib import admin
8
+ from django.utils.html import format_html
9
+ from django.http import HttpRequest
10
+ from typing import Any
11
+
12
+ from unfold.admin import ModelAdmin
13
+ from unfold.decorators import display, action
14
+
15
+ from ..models import CloudflareApiKey
16
+
17
+
18
+ @admin.register(CloudflareApiKey)
19
+ class CloudflareApiKeyAdmin(ModelAdmin):
20
+ """Admin interface for CloudflareApiKey model."""
21
+
22
+ list_display = [
23
+ 'status_display',
24
+ 'name',
25
+ 'description_preview',
26
+ 'active_badge',
27
+ 'default_badge',
28
+ 'sites_count',
29
+ 'last_used_at',
30
+ 'created_at'
31
+ ]
32
+
33
+ list_display_links = ['name']
34
+
35
+ search_fields = ['name', 'description', 'account_id']
36
+
37
+ list_filter = [
38
+ 'is_active',
39
+ 'is_default',
40
+ 'created_at',
41
+ 'last_used_at'
42
+ ]
43
+
44
+ readonly_fields = [
45
+ 'created_at',
46
+ 'updated_at',
47
+ 'last_used_at',
48
+ 'sites_using_key'
49
+ ]
50
+
51
+ fieldsets = [
52
+ ('Basic Information', {
53
+ 'fields': ['name', 'description']
54
+ }),
55
+ ('Cloudflare Configuration', {
56
+ 'fields': ['api_token', 'account_id'],
57
+ 'classes': ['collapse']
58
+ }),
59
+ ('Settings', {
60
+ 'fields': ['is_active', 'is_default']
61
+ }),
62
+ ('Timestamps', {
63
+ 'fields': ['created_at', 'updated_at', 'last_used_at'],
64
+ 'classes': ['collapse']
65
+ }),
66
+ ('Usage', {
67
+ 'fields': ['sites_using_key'],
68
+ 'classes': ['collapse']
69
+ })
70
+ ]
71
+
72
+ actions = [
73
+ 'make_default_action',
74
+ 'activate_keys_action',
75
+ 'deactivate_keys_action'
76
+ ]
77
+
78
+ # Display methods
79
+
80
+ @display(description="Status")
81
+ def status_display(self, obj: CloudflareApiKey) -> str:
82
+ """Display status with emoji."""
83
+ if obj.is_active:
84
+ if obj.is_default:
85
+ return format_html('<span style="color: green;">🔑 {} (Default)</span>', obj.name)
86
+ else:
87
+ return format_html('<span style="color: green;">🔑 {}</span>', obj.name)
88
+ else:
89
+ return format_html('<span style="color: red;">🔒 {}</span>', obj.name)
90
+
91
+ @display(description="Description")
92
+ def description_preview(self, obj: CloudflareApiKey) -> str:
93
+ """Show description preview."""
94
+ if not obj.description:
95
+ return "-"
96
+
97
+ preview = obj.description[:50]
98
+ if len(obj.description) > 50:
99
+ preview += "..."
100
+
101
+ return preview
102
+
103
+ @display(description="Active")
104
+ def active_badge(self, obj: CloudflareApiKey) -> str:
105
+ """Display active status badge."""
106
+ if obj.is_active:
107
+ return format_html('<span class="badge badge-success">Active</span>')
108
+ else:
109
+ return format_html('<span class="badge badge-secondary">Inactive</span>')
110
+
111
+ @display(description="Default")
112
+ def default_badge(self, obj: CloudflareApiKey) -> str:
113
+ """Display default status badge."""
114
+ if obj.is_default:
115
+ return format_html('<span class="badge badge-primary">Default</span>')
116
+ else:
117
+ return "-"
118
+
119
+ @display(description="Sites")
120
+ def sites_count(self, obj: CloudflareApiKey) -> str:
121
+ """Display count of sites using this key."""
122
+ count = obj.cloudflaresite_set.count()
123
+ if count > 0:
124
+ return f"{count} sites"
125
+ return "No sites"
126
+
127
+ def sites_using_key(self, obj: CloudflareApiKey) -> str:
128
+ """Show sites using this API key."""
129
+ sites = obj.cloudflaresite_set.all()[:10] # Limit to 10 for display
130
+
131
+ if not sites:
132
+ return "No sites using this key"
133
+
134
+ html = "<ul>"
135
+ for site in sites:
136
+ status_emoji = "🔧" if site.maintenance_active else "🟢"
137
+ html += f"<li>{status_emoji} {site.name} ({site.domain})</li>"
138
+
139
+ html += "</ul>"
140
+
141
+ total_count = obj.cloudflaresite_set.count()
142
+ if total_count > 10:
143
+ html += f"<p><em>... and {total_count - 10} more sites</em></p>"
144
+
145
+ return format_html(html)
146
+
147
+ sites_using_key.short_description = "Sites Using This Key"
148
+
149
+ # Admin Actions
150
+
151
+ @action(description="🔑 Make default API key")
152
+ def make_default_action(self, request: HttpRequest, queryset) -> None:
153
+ """Make selected key the default."""
154
+ if queryset.count() > 1:
155
+ self.message_user(request, "Please select only one API key to make default.", level='error')
156
+ return
157
+
158
+ key = queryset.first()
159
+ if key:
160
+ # This will automatically set others to non-default via the model's save method
161
+ key.is_default = True
162
+ key.save()
163
+ self.message_user(request, f"'{key.name}' is now the default API key.")
164
+
165
+ @action(description="✅ Activate API keys")
166
+ def activate_keys_action(self, request: HttpRequest, queryset) -> None:
167
+ """Activate selected API keys."""
168
+ count = queryset.update(is_active=True)
169
+ self.message_user(request, f"Successfully activated {count} API keys.")
170
+
171
+ @action(description="❌ Deactivate API keys")
172
+ def deactivate_keys_action(self, request: HttpRequest, queryset) -> None:
173
+ """Deactivate selected API keys."""
174
+ # Don't allow deactivating the default key
175
+ default_keys = queryset.filter(is_default=True)
176
+ if default_keys.exists():
177
+ self.message_user(
178
+ request,
179
+ "Cannot deactivate default API key. Please set another key as default first.",
180
+ level='error'
181
+ )
182
+ return
183
+
184
+ count = queryset.update(is_active=False)
185
+ self.message_user(request, f"Successfully deactivated {count} API keys.")
@@ -0,0 +1,156 @@
1
+ """
2
+ MaintenanceLog admin with Unfold styling.
3
+
4
+ Read-only admin interface for viewing maintenance operation logs.
5
+ """
6
+
7
+ from django.contrib import admin
8
+ from django.utils.html import format_html
9
+ from django.http import HttpRequest
10
+ from typing import Any
11
+ import json
12
+
13
+ from unfold.admin import ModelAdmin
14
+ from unfold.decorators import display
15
+
16
+ from ..models import MaintenanceLog
17
+
18
+
19
+ @admin.register(MaintenanceLog)
20
+ class MaintenanceLogAdmin(ModelAdmin):
21
+ """Admin interface for MaintenanceLog model."""
22
+
23
+ list_display = [
24
+ 'status_display',
25
+ 'site',
26
+ 'action_display',
27
+ 'created_at',
28
+ 'duration_display',
29
+ 'error_preview'
30
+ ]
31
+
32
+ list_filter = [
33
+ 'action',
34
+ 'status',
35
+ 'created_at',
36
+ 'site'
37
+ ]
38
+
39
+ search_fields = [
40
+ 'site__name',
41
+ 'site__domain',
42
+ 'reason',
43
+ 'error_message'
44
+ ]
45
+
46
+ readonly_fields = [
47
+ 'site',
48
+ 'action',
49
+ 'status',
50
+ 'reason',
51
+ 'error_message',
52
+ 'cloudflare_response',
53
+ 'created_at',
54
+ 'duration_seconds',
55
+ 'cloudflare_response_formatted'
56
+ ]
57
+
58
+ fieldsets = [
59
+ ('Operation Details', {
60
+ 'fields': ['site', 'action', 'status', 'created_at', 'duration_seconds']
61
+ }),
62
+ ('Additional Information', {
63
+ 'fields': ['reason', 'error_message']
64
+ }),
65
+ ('Cloudflare Response', {
66
+ 'fields': ['cloudflare_response_formatted'],
67
+ 'classes': ['collapse']
68
+ })
69
+ ]
70
+
71
+ ordering = ['-created_at']
72
+
73
+ def has_add_permission(self, request: HttpRequest) -> bool:
74
+ """Logs are created automatically, no manual adding."""
75
+ return False
76
+
77
+ def has_change_permission(self, request: HttpRequest, obj: Any = None) -> bool:
78
+ """Logs are read-only."""
79
+ return False
80
+
81
+ def has_delete_permission(self, request: HttpRequest, obj: Any = None) -> bool:
82
+ """Allow deletion of old logs."""
83
+ return True
84
+
85
+ # Display methods
86
+
87
+ @display(description="Status", ordering="status")
88
+ def status_display(self, obj: MaintenanceLog) -> str:
89
+ """Display status with emoji and badge."""
90
+ status_emoji = {
91
+ MaintenanceLog.Status.SUCCESS: "✅",
92
+ MaintenanceLog.Status.FAILED: "❌",
93
+ MaintenanceLog.Status.PENDING: "⏳"
94
+ }.get(obj.status, "❓")
95
+
96
+ color_class = {
97
+ MaintenanceLog.Status.SUCCESS: "badge-success",
98
+ MaintenanceLog.Status.FAILED: "badge-danger",
99
+ MaintenanceLog.Status.PENDING: "badge-warning"
100
+ }.get(obj.status, "badge-secondary")
101
+
102
+ return format_html(
103
+ '<span class="badge {}">{} {}</span>',
104
+ color_class, status_emoji, obj.get_status_display()
105
+ )
106
+
107
+ @display(description="Action", ordering="action")
108
+ def action_display(self, obj: MaintenanceLog) -> str:
109
+ """Display action with icon."""
110
+ action_icons = {
111
+ MaintenanceLog.Action.ENABLE: "🔧",
112
+ MaintenanceLog.Action.DISABLE: "🟢",
113
+ MaintenanceLog.Action.SYNC: "🔄",
114
+ MaintenanceLog.Action.ERROR: "❌"
115
+ }
116
+
117
+ icon = action_icons.get(obj.action, "❓")
118
+ return f"{icon} {obj.get_action_display()}"
119
+
120
+ @display(description="Duration")
121
+ def duration_display(self, obj: MaintenanceLog) -> str:
122
+ """Display operation duration."""
123
+ if not obj.duration_seconds:
124
+ return "-"
125
+
126
+ if obj.duration_seconds < 60:
127
+ return f"{obj.duration_seconds}s"
128
+ else:
129
+ minutes = obj.duration_seconds // 60
130
+ seconds = obj.duration_seconds % 60
131
+ return f"{minutes}m {seconds}s"
132
+
133
+ @display(description="Error")
134
+ def error_preview(self, obj: MaintenanceLog) -> str:
135
+ """Show error message preview."""
136
+ if not obj.error_message:
137
+ return "-"
138
+
139
+ preview = obj.error_message[:100]
140
+ if len(obj.error_message) > 100:
141
+ preview += "..."
142
+
143
+ return format_html('<span style="color: red; font-family: monospace;">{}</span>', preview)
144
+
145
+ def cloudflare_response_formatted(self, obj: MaintenanceLog) -> str:
146
+ """Format Cloudflare response for display."""
147
+ if not obj.cloudflare_response:
148
+ return "No response data"
149
+
150
+ try:
151
+ formatted = json.dumps(obj.cloudflare_response, indent=2)
152
+ return format_html('<pre style="background: #f8f8f8; padding: 10px; overflow: auto;">{}</pre>', formatted)
153
+ except Exception:
154
+ return str(obj.cloudflare_response)
155
+
156
+ cloudflare_response_formatted.short_description = "Cloudflare Response (Formatted)"