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.
- django_cfg/__init__.py +1 -1
- django_cfg/apps/accounts/models/__init__.py +68 -0
- django_cfg/apps/accounts/models/activity.py +34 -0
- django_cfg/apps/accounts/models/auth.py +50 -0
- django_cfg/apps/accounts/models/base.py +8 -0
- django_cfg/apps/accounts/models/choices.py +32 -0
- django_cfg/apps/accounts/models/integrations.py +75 -0
- django_cfg/apps/accounts/models/registration.py +52 -0
- django_cfg/apps/accounts/models/user.py +80 -0
- django_cfg/apps/maintenance/__init__.py +53 -24
- django_cfg/apps/maintenance/admin/__init__.py +7 -18
- django_cfg/apps/maintenance/admin/api_key_admin.py +185 -0
- django_cfg/apps/maintenance/admin/log_admin.py +156 -0
- django_cfg/apps/maintenance/admin/scheduled_admin.py +390 -0
- django_cfg/apps/maintenance/admin/site_admin.py +448 -0
- django_cfg/apps/maintenance/apps.py +9 -96
- django_cfg/apps/maintenance/management/commands/maintenance.py +193 -307
- django_cfg/apps/maintenance/management/commands/process_scheduled_maintenance.py +241 -0
- django_cfg/apps/maintenance/management/commands/sync_cloudflare.py +152 -111
- django_cfg/apps/maintenance/managers/__init__.py +7 -12
- django_cfg/apps/maintenance/managers/cloudflare_site_manager.py +192 -0
- django_cfg/apps/maintenance/managers/maintenance_log_manager.py +151 -0
- django_cfg/apps/maintenance/migrations/0001_initial.py +145 -705
- django_cfg/apps/maintenance/migrations/0002_cloudflaresite_maintenance_url.py +21 -0
- django_cfg/apps/maintenance/models/__init__.py +23 -21
- django_cfg/apps/maintenance/models/cloudflare_api_key.py +109 -0
- django_cfg/apps/maintenance/models/cloudflare_site.py +125 -0
- django_cfg/apps/maintenance/models/maintenance_log.py +131 -0
- django_cfg/apps/maintenance/models/scheduled_maintenance.py +307 -0
- django_cfg/apps/maintenance/services/__init__.py +37 -16
- django_cfg/apps/maintenance/services/bulk_operations_service.py +400 -0
- django_cfg/apps/maintenance/services/maintenance_service.py +230 -0
- django_cfg/apps/maintenance/services/scheduled_maintenance_service.py +381 -0
- django_cfg/apps/maintenance/services/site_sync_service.py +390 -0
- django_cfg/apps/maintenance/utils/__init__.py +12 -0
- django_cfg/apps/maintenance/utils/retry_utils.py +109 -0
- django_cfg/config.py +3 -0
- django_cfg/core/config.py +4 -6
- django_cfg/modules/django_unfold/dashboard.py +4 -5
- {django_cfg-1.2.16.dist-info → django_cfg-1.2.18.dist-info}/METADATA +52 -1
- {django_cfg-1.2.16.dist-info → django_cfg-1.2.18.dist-info}/RECORD +45 -55
- django_cfg/apps/maintenance/README.md +0 -305
- django_cfg/apps/maintenance/admin/deployments_admin.py +0 -251
- django_cfg/apps/maintenance/admin/events_admin.py +0 -374
- django_cfg/apps/maintenance/admin/monitoring_admin.py +0 -215
- django_cfg/apps/maintenance/admin/sites_admin.py +0 -464
- django_cfg/apps/maintenance/managers/deployments.py +0 -287
- django_cfg/apps/maintenance/managers/events.py +0 -374
- django_cfg/apps/maintenance/managers/monitoring.py +0 -301
- django_cfg/apps/maintenance/managers/sites.py +0 -335
- django_cfg/apps/maintenance/models/cloudflare.py +0 -316
- django_cfg/apps/maintenance/models/maintenance.py +0 -334
- django_cfg/apps/maintenance/models/monitoring.py +0 -393
- django_cfg/apps/maintenance/models/sites.py +0 -419
- django_cfg/apps/maintenance/serializers/__init__.py +0 -60
- django_cfg/apps/maintenance/serializers/actions.py +0 -310
- django_cfg/apps/maintenance/serializers/base.py +0 -44
- django_cfg/apps/maintenance/serializers/deployments.py +0 -209
- django_cfg/apps/maintenance/serializers/events.py +0 -210
- django_cfg/apps/maintenance/serializers/monitoring.py +0 -278
- django_cfg/apps/maintenance/serializers/sites.py +0 -213
- django_cfg/apps/maintenance/services/README.md +0 -168
- django_cfg/apps/maintenance/services/cloudflare_client.py +0 -441
- django_cfg/apps/maintenance/services/dns_manager.py +0 -497
- django_cfg/apps/maintenance/services/maintenance_manager.py +0 -504
- django_cfg/apps/maintenance/services/site_sync.py +0 -448
- django_cfg/apps/maintenance/services/sync_command_service.py +0 -330
- django_cfg/apps/maintenance/services/worker_manager.py +0 -264
- django_cfg/apps/maintenance/signals.py +0 -38
- django_cfg/apps/maintenance/urls.py +0 -36
- django_cfg/apps/maintenance/views/__init__.py +0 -18
- django_cfg/apps/maintenance/views/base.py +0 -61
- django_cfg/apps/maintenance/views/deployments.py +0 -175
- django_cfg/apps/maintenance/views/events.py +0 -204
- django_cfg/apps/maintenance/views/monitoring.py +0 -213
- django_cfg/apps/maintenance/views/sites.py +0 -338
- django_cfg/models/cloudflare.py +0 -316
- /django_cfg/apps/accounts/{models.py → __models.py} +0 -0
- {django_cfg-1.2.16.dist-info → django_cfg-1.2.18.dist-info}/WHEEL +0 -0
- {django_cfg-1.2.16.dist-info → django_cfg-1.2.18.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.2.16.dist-info → django_cfg-1.2.18.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,307 @@
|
|
1
|
+
"""
|
2
|
+
Scheduled maintenance model.
|
3
|
+
|
4
|
+
Allows planning and automatic execution of maintenance windows.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from django.db import models
|
8
|
+
from django.utils import timezone
|
9
|
+
from django.core.exceptions import ValidationError
|
10
|
+
from datetime import datetime, timedelta
|
11
|
+
from typing import Optional, Dict, Any
|
12
|
+
|
13
|
+
|
14
|
+
class ScheduledMaintenance(models.Model):
|
15
|
+
"""Scheduled maintenance events for sites."""
|
16
|
+
|
17
|
+
class Status(models.TextChoices):
|
18
|
+
SCHEDULED = "scheduled", "Scheduled"
|
19
|
+
ACTIVE = "active", "Active"
|
20
|
+
COMPLETED = "completed", "Completed"
|
21
|
+
CANCELLED = "cancelled", "Cancelled"
|
22
|
+
FAILED = "failed", "Failed"
|
23
|
+
|
24
|
+
class Priority(models.TextChoices):
|
25
|
+
LOW = "low", "Low"
|
26
|
+
NORMAL = "normal", "Normal"
|
27
|
+
HIGH = "high", "High"
|
28
|
+
CRITICAL = "critical", "Critical"
|
29
|
+
|
30
|
+
# Basic Information
|
31
|
+
title = models.CharField(max_length=200, help_text="Maintenance event title")
|
32
|
+
description = models.TextField(blank=True, help_text="Detailed description of maintenance")
|
33
|
+
|
34
|
+
# Scheduling
|
35
|
+
scheduled_start = models.DateTimeField(help_text="When maintenance should start")
|
36
|
+
estimated_duration = models.DurationField(help_text="Expected duration of maintenance")
|
37
|
+
scheduled_end = models.DateTimeField(editable=False, help_text="Auto-calculated end time")
|
38
|
+
|
39
|
+
# Target Sites
|
40
|
+
sites = models.ManyToManyField(
|
41
|
+
'CloudflareSite',
|
42
|
+
related_name='scheduled_maintenances',
|
43
|
+
help_text="Sites affected by this maintenance"
|
44
|
+
)
|
45
|
+
|
46
|
+
# Execution Details
|
47
|
+
status = models.CharField(
|
48
|
+
max_length=20,
|
49
|
+
choices=Status.choices,
|
50
|
+
default=Status.SCHEDULED
|
51
|
+
)
|
52
|
+
priority = models.CharField(
|
53
|
+
max_length=20,
|
54
|
+
choices=Priority.choices,
|
55
|
+
default=Priority.NORMAL
|
56
|
+
)
|
57
|
+
|
58
|
+
actual_start = models.DateTimeField(null=True, blank=True)
|
59
|
+
actual_end = models.DateTimeField(null=True, blank=True)
|
60
|
+
|
61
|
+
# Configuration
|
62
|
+
maintenance_message = models.TextField(
|
63
|
+
blank=True,
|
64
|
+
help_text="Custom message to display during maintenance"
|
65
|
+
)
|
66
|
+
template = models.CharField(
|
67
|
+
max_length=50,
|
68
|
+
default="modern",
|
69
|
+
choices=[
|
70
|
+
("modern", "Modern"),
|
71
|
+
("simple", "Simple"),
|
72
|
+
("premium", "Premium"),
|
73
|
+
("minimal", "Minimal"),
|
74
|
+
],
|
75
|
+
help_text="Maintenance page template"
|
76
|
+
)
|
77
|
+
|
78
|
+
# Automation Settings
|
79
|
+
auto_enable = models.BooleanField(
|
80
|
+
default=True,
|
81
|
+
help_text="Automatically enable maintenance at scheduled time"
|
82
|
+
)
|
83
|
+
auto_disable = models.BooleanField(
|
84
|
+
default=True,
|
85
|
+
help_text="Automatically disable maintenance after duration"
|
86
|
+
)
|
87
|
+
|
88
|
+
# Notifications
|
89
|
+
notify_before = models.DurationField(
|
90
|
+
default=timedelta(hours=1),
|
91
|
+
help_text="Send notification before maintenance starts"
|
92
|
+
)
|
93
|
+
notify_on_start = models.BooleanField(default=True)
|
94
|
+
notify_on_complete = models.BooleanField(default=True)
|
95
|
+
|
96
|
+
# Metadata
|
97
|
+
created_at = models.DateTimeField(auto_now_add=True)
|
98
|
+
updated_at = models.DateTimeField(auto_now=True)
|
99
|
+
created_by = models.CharField(max_length=100, blank=True, help_text="Who created this maintenance")
|
100
|
+
|
101
|
+
# Execution Results
|
102
|
+
execution_log = models.JSONField(
|
103
|
+
default=dict,
|
104
|
+
blank=True,
|
105
|
+
help_text="Log of execution steps and results"
|
106
|
+
)
|
107
|
+
|
108
|
+
class Meta:
|
109
|
+
ordering = ['scheduled_start']
|
110
|
+
verbose_name = "Scheduled Maintenance"
|
111
|
+
verbose_name_plural = "Scheduled Maintenances"
|
112
|
+
indexes = [
|
113
|
+
models.Index(fields=['status', 'scheduled_start']),
|
114
|
+
models.Index(fields=['scheduled_start']),
|
115
|
+
models.Index(fields=['status']),
|
116
|
+
]
|
117
|
+
|
118
|
+
def save(self, *args, **kwargs):
|
119
|
+
"""Auto-calculate scheduled_end."""
|
120
|
+
if self.scheduled_start and self.estimated_duration:
|
121
|
+
self.scheduled_end = self.scheduled_start + self.estimated_duration
|
122
|
+
super().save(*args, **kwargs)
|
123
|
+
|
124
|
+
def clean(self):
|
125
|
+
"""Validate the scheduled maintenance."""
|
126
|
+
if self.scheduled_start and self.scheduled_start <= timezone.now():
|
127
|
+
if self.status == self.Status.SCHEDULED:
|
128
|
+
raise ValidationError("Scheduled start time must be in the future")
|
129
|
+
|
130
|
+
if self.estimated_duration and self.estimated_duration <= timedelta(0):
|
131
|
+
raise ValidationError("Estimated duration must be positive")
|
132
|
+
|
133
|
+
def __str__(self):
|
134
|
+
status_emoji = {
|
135
|
+
self.Status.SCHEDULED: "📅",
|
136
|
+
self.Status.ACTIVE: "🔧",
|
137
|
+
self.Status.COMPLETED: "✅",
|
138
|
+
self.Status.CANCELLED: "❌",
|
139
|
+
self.Status.FAILED: "💥"
|
140
|
+
}.get(self.status, "❓")
|
141
|
+
|
142
|
+
return f"{status_emoji} {self.title} - {self.scheduled_start.strftime('%Y-%m-%d %H:%M')}"
|
143
|
+
|
144
|
+
@property
|
145
|
+
def affected_sites_count(self) -> int:
|
146
|
+
"""Count total affected sites."""
|
147
|
+
return self.sites.count()
|
148
|
+
|
149
|
+
@property
|
150
|
+
def is_due(self) -> bool:
|
151
|
+
"""Check if maintenance is due to start."""
|
152
|
+
return (
|
153
|
+
self.status == self.Status.SCHEDULED and
|
154
|
+
timezone.now() >= self.scheduled_start
|
155
|
+
)
|
156
|
+
|
157
|
+
@property
|
158
|
+
def is_overdue(self) -> bool:
|
159
|
+
"""Check if maintenance should have ended."""
|
160
|
+
return (
|
161
|
+
self.status == self.Status.ACTIVE and
|
162
|
+
timezone.now() >= self.scheduled_end
|
163
|
+
)
|
164
|
+
|
165
|
+
@property
|
166
|
+
def time_until_start(self) -> Optional[timedelta]:
|
167
|
+
"""Time until maintenance starts."""
|
168
|
+
if self.status == self.Status.SCHEDULED:
|
169
|
+
return self.scheduled_start - timezone.now()
|
170
|
+
return None
|
171
|
+
|
172
|
+
@property
|
173
|
+
def time_until_end(self) -> Optional[timedelta]:
|
174
|
+
"""Time until maintenance ends."""
|
175
|
+
if self.status == self.Status.ACTIVE:
|
176
|
+
return self.scheduled_end - timezone.now()
|
177
|
+
return None
|
178
|
+
|
179
|
+
@property
|
180
|
+
def actual_duration(self) -> Optional[timedelta]:
|
181
|
+
"""Actual duration of maintenance."""
|
182
|
+
if self.actual_start and self.actual_end:
|
183
|
+
return self.actual_end - self.actual_start
|
184
|
+
return None
|
185
|
+
|
186
|
+
def start_maintenance(self) -> Dict[str, Any]:
|
187
|
+
"""Start the scheduled maintenance."""
|
188
|
+
if self.status != self.Status.SCHEDULED:
|
189
|
+
return {
|
190
|
+
'success': False,
|
191
|
+
'error': f'Cannot start maintenance in {self.status} status'
|
192
|
+
}
|
193
|
+
|
194
|
+
from ..services.bulk_operations_service import bulk_operations
|
195
|
+
|
196
|
+
# Update status
|
197
|
+
self.status = self.Status.ACTIVE
|
198
|
+
self.actual_start = timezone.now()
|
199
|
+
self.save()
|
200
|
+
|
201
|
+
# Enable maintenance for all sites
|
202
|
+
sites_queryset = bulk_operations.sites(self.sites.all())
|
203
|
+
result = sites_queryset.enable_maintenance(
|
204
|
+
reason=self.maintenance_message or f"Scheduled maintenance: {self.title}",
|
205
|
+
template=self.template
|
206
|
+
)
|
207
|
+
|
208
|
+
# Log execution
|
209
|
+
self.execution_log['start'] = {
|
210
|
+
'timestamp': timezone.now().isoformat(),
|
211
|
+
'result': result,
|
212
|
+
'sites_affected': result['total']
|
213
|
+
}
|
214
|
+
self.save()
|
215
|
+
|
216
|
+
return {
|
217
|
+
'success': True,
|
218
|
+
'sites_affected': result['total'],
|
219
|
+
'successful': len(result['successful']),
|
220
|
+
'failed': len(result['failed'])
|
221
|
+
}
|
222
|
+
|
223
|
+
def complete_maintenance(self) -> Dict[str, Any]:
|
224
|
+
"""Complete the scheduled maintenance."""
|
225
|
+
if self.status != self.Status.ACTIVE:
|
226
|
+
return {
|
227
|
+
'success': False,
|
228
|
+
'error': f'Cannot complete maintenance in {self.status} status'
|
229
|
+
}
|
230
|
+
|
231
|
+
from ..services.bulk_operations_service import bulk_operations
|
232
|
+
|
233
|
+
# Disable maintenance for all sites
|
234
|
+
sites_queryset = bulk_operations.sites(self.sites.all())
|
235
|
+
result = sites_queryset.disable_maintenance()
|
236
|
+
|
237
|
+
# Update status
|
238
|
+
self.status = self.Status.COMPLETED
|
239
|
+
self.actual_end = timezone.now()
|
240
|
+
|
241
|
+
# Log execution
|
242
|
+
self.execution_log['complete'] = {
|
243
|
+
'timestamp': timezone.now().isoformat(),
|
244
|
+
'result': result,
|
245
|
+
'sites_affected': result['total']
|
246
|
+
}
|
247
|
+
self.save()
|
248
|
+
|
249
|
+
return {
|
250
|
+
'success': True,
|
251
|
+
'sites_affected': result['total'],
|
252
|
+
'successful': len(result['successful']),
|
253
|
+
'failed': len(result['failed']),
|
254
|
+
'actual_duration': self.actual_duration.total_seconds() if self.actual_duration else None
|
255
|
+
}
|
256
|
+
|
257
|
+
def cancel_maintenance(self, reason: str = "") -> Dict[str, Any]:
|
258
|
+
"""Cancel the scheduled maintenance."""
|
259
|
+
if self.status in [self.Status.COMPLETED, self.Status.CANCELLED]:
|
260
|
+
return {
|
261
|
+
'success': False,
|
262
|
+
'error': f'Cannot cancel maintenance in {self.status} status'
|
263
|
+
}
|
264
|
+
|
265
|
+
# If active, disable maintenance first
|
266
|
+
if self.status == self.Status.ACTIVE:
|
267
|
+
self.complete_maintenance()
|
268
|
+
|
269
|
+
# Update status
|
270
|
+
self.status = self.Status.CANCELLED
|
271
|
+
|
272
|
+
# Log cancellation
|
273
|
+
self.execution_log['cancelled'] = {
|
274
|
+
'timestamp': timezone.now().isoformat(),
|
275
|
+
'reason': reason
|
276
|
+
}
|
277
|
+
self.save()
|
278
|
+
|
279
|
+
return {
|
280
|
+
'success': True,
|
281
|
+
'reason': reason
|
282
|
+
}
|
283
|
+
|
284
|
+
@classmethod
|
285
|
+
def get_due_maintenances(cls):
|
286
|
+
"""Get maintenances that are due to start."""
|
287
|
+
return cls.objects.filter(
|
288
|
+
status=cls.Status.SCHEDULED,
|
289
|
+
scheduled_start__lte=timezone.now()
|
290
|
+
)
|
291
|
+
|
292
|
+
@classmethod
|
293
|
+
def get_overdue_maintenances(cls):
|
294
|
+
"""Get active maintenances that should have ended."""
|
295
|
+
return cls.objects.filter(
|
296
|
+
status=cls.Status.ACTIVE,
|
297
|
+
scheduled_end__lte=timezone.now()
|
298
|
+
)
|
299
|
+
|
300
|
+
@classmethod
|
301
|
+
def get_upcoming_maintenances(cls, hours: int = 24):
|
302
|
+
"""Get maintenances starting within specified hours."""
|
303
|
+
return cls.objects.filter(
|
304
|
+
status=cls.Status.SCHEDULED,
|
305
|
+
scheduled_start__lte=timezone.now() + timedelta(hours=hours),
|
306
|
+
scheduled_start__gte=timezone.now()
|
307
|
+
)
|
@@ -1,21 +1,42 @@
|
|
1
1
|
"""
|
2
|
-
|
2
|
+
Simplified maintenance services.
|
3
3
|
|
4
|
-
|
4
|
+
Clean imports for the service classes with lazy loading.
|
5
5
|
"""
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
7
|
+
# Lazy import registry to avoid Django initialization issues
|
8
|
+
_LAZY_IMPORTS = {
|
9
|
+
# Services
|
10
|
+
'MaintenanceService': ('django_cfg.apps.maintenance.services.maintenance_service', 'MaintenanceService'),
|
11
|
+
'SiteSyncService': ('django_cfg.apps.maintenance.services.site_sync_service', 'SiteSyncService'),
|
12
|
+
'BulkOperationsService': ('django_cfg.apps.maintenance.services.bulk_operations_service', 'BulkOperationsService'),
|
13
|
+
'ScheduledMaintenanceService': ('django_cfg.apps.maintenance.services.scheduled_maintenance_service', 'ScheduledMaintenanceService'),
|
14
|
+
|
15
|
+
# Service functions
|
16
|
+
'enable_maintenance_for_domain': ('django_cfg.apps.maintenance.services.maintenance_service', 'enable_maintenance_for_domain'),
|
17
|
+
'disable_maintenance_for_domain': ('django_cfg.apps.maintenance.services.maintenance_service', 'disable_maintenance_for_domain'),
|
18
|
+
'sync_site_from_cloudflare': ('django_cfg.apps.maintenance.services.site_sync_service', 'sync_site_from_cloudflare'),
|
19
|
+
'bulk_operations': ('django_cfg.apps.maintenance.services.bulk_operations_service', 'bulk_operations'),
|
20
|
+
'scheduled_maintenance_service': ('django_cfg.apps.maintenance.services.scheduled_maintenance_service', 'scheduled_maintenance_service'),
|
21
|
+
'enable_maintenance_for_domains': ('django_cfg.apps.maintenance.services.bulk_operations_service', 'enable_maintenance_for_domains'),
|
22
|
+
'disable_maintenance_for_domains': ('django_cfg.apps.maintenance.services.bulk_operations_service', 'disable_maintenance_for_domains'),
|
23
|
+
'bulk_sync_all_sites': ('django_cfg.apps.maintenance.services.bulk_operations_service', 'bulk_sync_all_sites'),
|
24
|
+
'get_maintenance_status_report': ('django_cfg.apps.maintenance.services.bulk_operations_service', 'get_maintenance_status_report'),
|
25
|
+
'schedule_maintenance_for_sites': ('django_cfg.apps.maintenance.services.scheduled_maintenance_service', 'schedule_maintenance_for_sites'),
|
26
|
+
'process_scheduled_maintenances': ('django_cfg.apps.maintenance.services.scheduled_maintenance_service', 'process_scheduled_maintenances'),
|
27
|
+
}
|
13
28
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
29
|
+
|
30
|
+
def __getattr__(name: str):
|
31
|
+
"""Lazy import mechanism to avoid Django initialization issues."""
|
32
|
+
if name in _LAZY_IMPORTS:
|
33
|
+
module_path, attr_name = _LAZY_IMPORTS[name]
|
34
|
+
|
35
|
+
import importlib
|
36
|
+
module = importlib.import_module(module_path)
|
37
|
+
return getattr(module, attr_name)
|
38
|
+
|
39
|
+
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
|
40
|
+
|
41
|
+
|
42
|
+
__all__ = list(_LAZY_IMPORTS.keys())
|