django-cfg 1.2.17__py3-none-any.whl → 1.2.19__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 +4 -0
  38. django_cfg/core/config.py +4 -6
  39. django_cfg/modules/django_unfold/dashboard.py +4 -5
  40. {django_cfg-1.2.17.dist-info → django_cfg-1.2.19.dist-info}/METADATA +52 -1
  41. {django_cfg-1.2.17.dist-info → django_cfg-1.2.19.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.17.dist-info → django_cfg-1.2.19.dist-info}/WHEEL +0 -0
  80. {django_cfg-1.2.17.dist-info → django_cfg-1.2.19.dist-info}/entry_points.txt +0 -0
  81. {django_cfg-1.2.17.dist-info → django_cfg-1.2.19.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
- Modern Cloudflare services using official cloudflare library.
2
+ Simplified maintenance services.
3
3
 
4
- Provides clean, type-safe, and reliable integration with Cloudflare API v4.
4
+ Clean imports for the service classes with lazy loading.
5
5
  """
6
6
 
7
- from .cloudflare_client import CloudflareClient
8
- from .maintenance_manager import MaintenanceManager
9
- from .site_sync import SiteSyncService
10
- from .worker_manager import WorkerManager
11
- from .dns_manager import DNSManager
12
- from .sync_command_service import SyncCommandService
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
- __all__ = [
15
- 'CloudflareClient',
16
- 'MaintenanceManager',
17
- 'SiteSyncService',
18
- 'WorkerManager',
19
- 'DNSManager',
20
- 'SyncCommandService',
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())