django-cfg 1.2.17__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.17.dist-info → django_cfg-1.2.18.dist-info}/METADATA +52 -1
  41. {django_cfg-1.2.17.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.17.dist-info → django_cfg-1.2.18.dist-info}/WHEEL +0 -0
  80. {django_cfg-1.2.17.dist-info → django_cfg-1.2.18.dist-info}/entry_points.txt +0 -0
  81. {django_cfg-1.2.17.dist-info → django_cfg-1.2.18.dist-info}/licenses/LICENSE +0 -0
django_cfg/__init__.py CHANGED
@@ -32,7 +32,7 @@ Example:
32
32
  default_app_config = "django_cfg.apps.DjangoCfgConfig"
33
33
 
34
34
  # Version information
35
- __version__ = "1.2.17"
35
+ __version__ = "1.2.18"
36
36
  __license__ = "MIT"
37
37
 
38
38
  # Import registry for organized lazy loading
@@ -0,0 +1,68 @@
1
+ """
2
+ Django CFG Accounts Models
3
+
4
+ This module provides all the models for the accounts app.
5
+ Models are organized by functionality:
6
+
7
+ - base: Base utilities and functions
8
+ - user: User model and related functionality
9
+ - registration: Registration and source tracking models
10
+ - auth: Authentication models (OTP, etc.)
11
+ - activity: User activity tracking models
12
+ - integrations: Third-party integrations models (Twilio, etc.)
13
+ """
14
+
15
+ # Import base utilities
16
+ from .base import user_avatar_path
17
+
18
+ # Import choices
19
+ from .choices import (
20
+ ActivityType,
21
+ TwilioResponseType,
22
+ TwilioServiceType,
23
+ )
24
+
25
+ # Import user models
26
+ from .user import CustomUser
27
+
28
+ # Import registration models
29
+ from .registration import (
30
+ RegistrationSource,
31
+ UserRegistrationSource,
32
+ )
33
+
34
+ # Import authentication models
35
+ from .auth import OTPSecret
36
+
37
+ # Import activity models
38
+ from .activity import UserActivity
39
+
40
+ # Import integration models
41
+ from .integrations import TwilioResponse
42
+
43
+ # Export all models
44
+ __all__ = [
45
+ # Base utilities
46
+ 'user_avatar_path',
47
+
48
+ # Choices
49
+ 'ActivityType',
50
+ 'TwilioResponseType',
51
+ 'TwilioServiceType',
52
+
53
+ # User models
54
+ 'CustomUser',
55
+
56
+ # Registration models
57
+ 'RegistrationSource',
58
+ 'UserRegistrationSource',
59
+
60
+ # Authentication models
61
+ 'OTPSecret',
62
+
63
+ # Activity models
64
+ 'UserActivity',
65
+
66
+ # Integration models
67
+ 'TwilioResponse',
68
+ ]
@@ -0,0 +1,34 @@
1
+ """
2
+ User activity tracking models.
3
+ """
4
+
5
+ from django.db import models
6
+
7
+ from .choices import ActivityType
8
+
9
+
10
+ class UserActivity(models.Model):
11
+ """
12
+ User activity log.
13
+ """
14
+
15
+ user = models.ForeignKey('CustomUser', on_delete=models.CASCADE, related_name='activities')
16
+ activity_type = models.CharField(max_length=20, choices=ActivityType.choices)
17
+ description = models.TextField(blank=True)
18
+ ip_address = models.GenericIPAddressField(null=True, blank=True)
19
+ user_agent = models.TextField(blank=True)
20
+
21
+ # Related objects (generic foreign key could be used here)
22
+ object_id = models.PositiveIntegerField(null=True, blank=True)
23
+ object_type = models.CharField(max_length=50, blank=True)
24
+
25
+ created_at = models.DateTimeField(auto_now_add=True)
26
+
27
+ class Meta:
28
+ app_label = 'django_cfg_accounts'
29
+ verbose_name = 'User Activity'
30
+ verbose_name_plural = 'User Activities'
31
+ ordering = ['-created_at']
32
+
33
+ def __str__(self):
34
+ return f"{self.user.username} - {self.get_activity_type_display()}"
@@ -0,0 +1,50 @@
1
+ """
2
+ Authentication models (OTP, etc.).
3
+ """
4
+
5
+ import random
6
+ import string
7
+ from datetime import timedelta
8
+
9
+ from django.db import models
10
+ from django.utils import timezone
11
+
12
+
13
+ class OTPSecret(models.Model):
14
+ """Stores One-Time Passwords for authentication."""
15
+
16
+ email = models.EmailField(db_index=True)
17
+ secret = models.CharField(max_length=6)
18
+ created_at = models.DateTimeField(auto_now_add=True)
19
+ expires_at = models.DateTimeField()
20
+ is_used = models.BooleanField(default=False)
21
+
22
+ def save(self, *args, **kwargs):
23
+ if not self.expires_at:
24
+ self.expires_at = timezone.now() + timedelta(minutes=10)
25
+ super().save(*args, **kwargs)
26
+
27
+ @staticmethod
28
+ def generate_otp(length=6):
29
+ """Generate random numeric OTP."""
30
+ return "".join(random.choices(string.digits, k=length))
31
+
32
+ @property
33
+ def is_valid(self):
34
+ """Check if OTP is still valid."""
35
+ return not self.is_used and timezone.now() < self.expires_at
36
+
37
+ def mark_used(self):
38
+ """Mark OTP as used."""
39
+ self.is_used = True
40
+ self.save(update_fields=["is_used"])
41
+
42
+ def __str__(self):
43
+ return f"OTP for {self.email}"
44
+
45
+ class Meta:
46
+ app_label = 'django_cfg_accounts'
47
+ ordering = ["-created_at"]
48
+ indexes = [
49
+ models.Index(fields=["email", "is_used", "expires_at"]),
50
+ ]
@@ -0,0 +1,8 @@
1
+ """
2
+ Base utilities and functions for accounts models.
3
+ """
4
+
5
+
6
+ def user_avatar_path(instance, filename):
7
+ """Generate file path for user avatar."""
8
+ return f"avatars/{instance.id}/{filename}"
@@ -0,0 +1,32 @@
1
+ """
2
+ Model choices constants for accounts app.
3
+ """
4
+
5
+ from django.db import models
6
+
7
+
8
+ class ActivityType(models.TextChoices):
9
+ """User activity types."""
10
+ LOGIN = 'login', 'Login'
11
+ LOGOUT = 'logout', 'Logout'
12
+ OTP_REQUESTED = 'otp_requested', 'OTP Requested'
13
+ OTP_VERIFIED = 'otp_verified', 'OTP Verified'
14
+ PROFILE_UPDATED = 'profile_updated', 'Profile Updated'
15
+ REGISTRATION = 'registration', 'Registration'
16
+
17
+
18
+ class TwilioResponseType(models.TextChoices):
19
+ """Twilio response types."""
20
+ API_SEND = 'api_send', 'API Send Request'
21
+ API_VERIFY = 'api_verify', 'API Verify Request'
22
+ WEBHOOK_STATUS = 'webhook_status', 'Webhook Status Update'
23
+ WEBHOOK_DELIVERY = 'webhook_delivery', 'Webhook Delivery Report'
24
+
25
+
26
+ class TwilioServiceType(models.TextChoices):
27
+ """Twilio service types."""
28
+ WHATSAPP = 'whatsapp', 'WhatsApp'
29
+ SMS = 'sms', 'SMS'
30
+ VOICE = 'voice', 'Voice'
31
+ EMAIL = 'email', 'Email'
32
+ VERIFY = 'verify', 'Verify API'
@@ -0,0 +1,75 @@
1
+ """
2
+ Third-party integrations models (Twilio, etc.).
3
+ """
4
+
5
+ from django.db import models
6
+
7
+ from .choices import TwilioResponseType, TwilioServiceType
8
+
9
+
10
+ class TwilioResponse(models.Model):
11
+ """Model for storing Twilio API responses and webhook data."""
12
+
13
+ response_type = models.CharField(max_length=20, choices=TwilioResponseType.choices)
14
+ service_type = models.CharField(max_length=10, choices=TwilioServiceType.choices)
15
+
16
+ # Twilio identifiers
17
+ message_sid = models.CharField(max_length=34, blank=True, help_text="Twilio Message SID")
18
+ verification_sid = models.CharField(max_length=34, blank=True, help_text="Twilio Verification SID")
19
+
20
+ # Request/Response data
21
+ request_data = models.JSONField(default=dict, help_text="Original request parameters")
22
+ response_data = models.JSONField(default=dict, help_text="Twilio API response")
23
+
24
+ # Status and error handling
25
+ status = models.CharField(max_length=20, blank=True, help_text="Message/Verification status")
26
+ error_code = models.CharField(max_length=10, blank=True, help_text="Twilio error code")
27
+ error_message = models.TextField(blank=True, help_text="Error description")
28
+
29
+ # Contact information
30
+ to_number = models.CharField(max_length=20, blank=True, help_text="Recipient phone/email")
31
+ from_number = models.CharField(max_length=20, blank=True, help_text="Sender phone/email")
32
+
33
+ # Pricing
34
+ price = models.DecimalField(max_digits=10, decimal_places=6, null=True, blank=True)
35
+ price_unit = models.CharField(max_length=3, blank=True, help_text="Currency code")
36
+
37
+ # Timestamps
38
+ created_at = models.DateTimeField(auto_now_add=True)
39
+ updated_at = models.DateTimeField(auto_now=True)
40
+ twilio_created_at = models.DateTimeField(null=True, blank=True, help_text="Timestamp from Twilio")
41
+
42
+ # Relations
43
+ otp_secret = models.ForeignKey(
44
+ 'OTPSecret',
45
+ on_delete=models.SET_NULL,
46
+ null=True,
47
+ blank=True,
48
+ related_name='twilio_responses',
49
+ help_text="Related OTP if applicable"
50
+ )
51
+
52
+ class Meta:
53
+ app_label = 'django_cfg_accounts'
54
+ verbose_name = 'Twilio Response'
55
+ verbose_name_plural = 'Twilio Responses'
56
+ ordering = ['-created_at']
57
+ indexes = [
58
+ models.Index(fields=['message_sid']),
59
+ models.Index(fields=['verification_sid']),
60
+ models.Index(fields=['status', 'created_at']),
61
+ models.Index(fields=['response_type', 'service_type']),
62
+ ]
63
+
64
+ def __str__(self):
65
+ return f"{self.get_response_type_display()} - {self.get_service_type_display()}"
66
+
67
+ @property
68
+ def has_error(self):
69
+ """Check if response has error."""
70
+ return bool(self.error_code or self.error_message)
71
+
72
+ @property
73
+ def is_successful(self):
74
+ """Check if response is successful."""
75
+ return not self.has_error and self.status in ['sent', 'delivered', 'approved']
@@ -0,0 +1,52 @@
1
+ """
2
+ Registration and source tracking models.
3
+ """
4
+
5
+ from django.db import models
6
+ from urllib.parse import urlparse
7
+
8
+
9
+ class RegistrationSource(models.Model):
10
+ """Model for tracking user registration sources/projects."""
11
+ url = models.URLField(unique=True, help_text="Source URL (e.g., https://unrealon.com)")
12
+ name = models.CharField(max_length=100, blank=True, help_text="Display name for the source")
13
+ description = models.TextField(blank=True, help_text="Optional description")
14
+ is_active = models.BooleanField(default=True, help_text="Whether this source is active")
15
+ created_at = models.DateTimeField(auto_now_add=True)
16
+ updated_at = models.DateTimeField(auto_now=True)
17
+
18
+ def __str__(self):
19
+ return self.name or self.get_domain()
20
+
21
+ def get_domain(self):
22
+ """Extract domain from URL."""
23
+ try:
24
+ parsed = urlparse(self.url)
25
+ return parsed.netloc
26
+ except:
27
+ return self.url
28
+
29
+ def get_display_name(self):
30
+ """Get display name or domain."""
31
+ return self.name or self.get_domain()
32
+
33
+ class Meta:
34
+ app_label = 'django_cfg_accounts'
35
+ verbose_name = "Registration Source"
36
+ verbose_name_plural = "Registration Sources"
37
+ ordering = ['-created_at']
38
+
39
+
40
+ class UserRegistrationSource(models.Model):
41
+ """Many-to-many relationship between users and registration sources."""
42
+ user = models.ForeignKey('CustomUser', on_delete=models.CASCADE, related_name='user_registration_sources')
43
+ source = models.ForeignKey(RegistrationSource, on_delete=models.CASCADE, related_name='user_registration_sources')
44
+ first_registration = models.BooleanField(default=True, help_text="Whether this was the first registration from this source")
45
+ registration_date = models.DateTimeField(auto_now_add=True)
46
+
47
+ class Meta:
48
+ app_label = 'django_cfg_accounts'
49
+ unique_together = ['user', 'source']
50
+ verbose_name = "User Registration Source"
51
+ verbose_name_plural = "User Registration Sources"
52
+ ordering = ['-registration_date']
@@ -0,0 +1,80 @@
1
+ """
2
+ User model and related functionality.
3
+ """
4
+
5
+ from typing import Optional, List
6
+ from django.contrib.auth.models import AbstractUser
7
+ from django.db import models
8
+
9
+ from ..managers.user_manager import UserManager
10
+ from .base import user_avatar_path
11
+
12
+
13
+ class CustomUser(AbstractUser):
14
+ """Simplified user model for OTP-only authentication."""
15
+
16
+ email = models.EmailField(unique=True)
17
+
18
+ # Profile fields
19
+ first_name = models.CharField(max_length=50, blank=True)
20
+ last_name = models.CharField(max_length=50, blank=True)
21
+ company = models.CharField(max_length=100, blank=True)
22
+ phone = models.CharField(max_length=20, blank=True)
23
+ position = models.CharField(max_length=100, blank=True)
24
+ avatar = models.ImageField(upload_to=user_avatar_path, blank=True, null=True)
25
+
26
+ # Profile metadata
27
+ updated_at = models.DateTimeField(auto_now=True)
28
+
29
+ # Managers
30
+ objects: UserManager = UserManager()
31
+
32
+ USERNAME_FIELD = "email"
33
+ REQUIRED_FIELDS = ["username"]
34
+
35
+ def __str__(self):
36
+ return self.email
37
+
38
+ @property
39
+ def is_admin(self) -> bool:
40
+ return self.is_superuser
41
+
42
+ @property
43
+ def full_name(self) -> str:
44
+ """Get user's full name."""
45
+ return self.__class__.objects.get_full_name(self)
46
+
47
+ @property
48
+ def initials(self) -> str:
49
+ """Get user's initials for avatar fallback."""
50
+ return self.__class__.objects.get_initials(self)
51
+
52
+ @property
53
+ def display_username(self) -> str:
54
+ """Get formatted username for display."""
55
+ return self.__class__.objects.get_display_username(self)
56
+
57
+ @property
58
+ def unanswered_messages_count(self) -> int:
59
+ """Get count of unanswered messages for the user."""
60
+ return self.__class__.objects.get_unanswered_messages_count(self)
61
+
62
+ def get_sources(self) -> List['RegistrationSource']:
63
+ """Get all sources associated with this user."""
64
+ from .registration import RegistrationSource
65
+ return RegistrationSource.objects.filter(user_registration_sources__user=self)
66
+
67
+ @property
68
+ def primary_source(self) -> Optional['RegistrationSource']:
69
+ """Get the first source where user registered."""
70
+ from .registration import UserRegistrationSource
71
+ user_source = UserRegistrationSource.objects.filter(
72
+ user=self,
73
+ first_registration=True
74
+ ).first()
75
+ return user_source.source if user_source else None
76
+
77
+ class Meta:
78
+ app_label = 'django_cfg_accounts'
79
+ verbose_name = "User"
80
+ verbose_name_plural = "Users"
@@ -1,27 +1,56 @@
1
1
  """
2
- Django-CFG Maintenance Application
3
-
4
- Multi-site maintenance mode management with Cloudflare integration.
5
- Provides zero-configuration setup and ORM-like interface for managing
6
- maintenance mode across multiple sites.
7
-
8
- Key Features:
9
- - Zero-configuration Cloudflare setup
10
- - Multi-site management with ORM-like queries
11
- - External monitoring for automatic maintenance mode
12
- - Rich Django admin interface
13
- - Scheduled maintenance support
14
- - Full audit trail and logging
15
-
16
- Example Usage:
17
- # Zero-config setup
18
- CLOUDFLARE_API_TOKEN = "your_token"
19
- CLOUDFLARE_DOMAIN = "example.com"
20
-
21
- # Multi-site management
22
- sites = multi_site_manager.sites(user)
23
- await sites.production().enable_maintenance()
24
- await sites.filter(project='client-a').disable_maintenance()
2
+ Simplified Django-CFG Maintenance Application.
3
+
4
+ A simple maintenance mode management app with Cloudflare integration.
5
+ Refactored from 119 files to ~10 files following KISS principles.
6
+
7
+ Proper structure:
8
+ - models/ - CloudflareSite and MaintenanceLog models
9
+ - services/ - MaintenanceService for Cloudflare operations
10
+ - admin/ - Simple admin interfaces
11
+ - management/commands/ - CLI commands
25
12
  """
26
13
 
27
- default_app_config = 'django_cfg.apps.maintenance.apps.MaintenanceConfig'
14
+ __version__ = "2.0.0"
15
+ __author__ = "Django-CFG Team"
16
+
17
+ # Lazy import registry to avoid Django initialization issues
18
+ _LAZY_IMPORTS = {
19
+ # Models
20
+ 'CloudflareApiKey': ('django_cfg.apps.maintenance.models', 'CloudflareApiKey'),
21
+ 'CloudflareSite': ('django_cfg.apps.maintenance.models', 'CloudflareSite'),
22
+ 'MaintenanceLog': ('django_cfg.apps.maintenance.models', 'MaintenanceLog'),
23
+ 'ScheduledMaintenance': ('django_cfg.apps.maintenance.models', 'ScheduledMaintenance'),
24
+
25
+ # Services
26
+ 'MaintenanceService': ('django_cfg.apps.maintenance.services', 'MaintenanceService'),
27
+ 'SiteSyncService': ('django_cfg.apps.maintenance.services', 'SiteSyncService'),
28
+ 'BulkOperationsService': ('django_cfg.apps.maintenance.services', 'BulkOperationsService'),
29
+ 'ScheduledMaintenanceService': ('django_cfg.apps.maintenance.services', 'ScheduledMaintenanceService'),
30
+ 'bulk_operations': ('django_cfg.apps.maintenance.services', 'bulk_operations'),
31
+ 'scheduled_maintenance_service': ('django_cfg.apps.maintenance.services', 'scheduled_maintenance_service'),
32
+ 'enable_maintenance_for_domain': ('django_cfg.apps.maintenance.services', 'enable_maintenance_for_domain'),
33
+ 'disable_maintenance_for_domain': ('django_cfg.apps.maintenance.services', 'disable_maintenance_for_domain'),
34
+ 'sync_site_from_cloudflare': ('django_cfg.apps.maintenance.services', 'sync_site_from_cloudflare'),
35
+ 'enable_maintenance_for_domains': ('django_cfg.apps.maintenance.services', 'enable_maintenance_for_domains'),
36
+ 'disable_maintenance_for_domains': ('django_cfg.apps.maintenance.services', 'disable_maintenance_for_domains'),
37
+ 'bulk_sync_all_sites': ('django_cfg.apps.maintenance.services', 'bulk_sync_all_sites'),
38
+ 'get_maintenance_status_report': ('django_cfg.apps.maintenance.services', 'get_maintenance_status_report'),
39
+ 'schedule_maintenance_for_sites': ('django_cfg.apps.maintenance.services', 'schedule_maintenance_for_sites'),
40
+ 'process_scheduled_maintenances': ('django_cfg.apps.maintenance.services', 'process_scheduled_maintenances'),
41
+ }
42
+
43
+
44
+ def __getattr__(name: str):
45
+ """Lazy import mechanism to avoid Django initialization issues."""
46
+ if name in _LAZY_IMPORTS:
47
+ module_path, attr_name = _LAZY_IMPORTS[name]
48
+
49
+ import importlib
50
+ module = importlib.import_module(module_path)
51
+ return getattr(module, attr_name)
52
+
53
+ raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
54
+
55
+
56
+ __all__ = list(_LAZY_IMPORTS.keys())
@@ -1,28 +1,17 @@
1
1
  """
2
2
  Maintenance admin interfaces.
3
3
 
4
- Rich Django admin interfaces for multi-site maintenance management.
5
- Following django-cfg patterns with Unfold optimization.
4
+ Decomposed admin interfaces with Unfold styling and action buttons.
6
5
  """
7
6
 
8
- # Import all admin classes to register them
9
- from .sites_admin import CloudflareSiteAdmin, SiteGroupAdmin
10
- from .events_admin import MaintenanceEventAdmin, MaintenanceLogAdmin
11
- from .monitoring_admin import MonitoringTargetAdmin
12
- from .deployments_admin import CloudflareDeploymentAdmin
7
+ from .api_key_admin import CloudflareApiKeyAdmin
8
+ from .site_admin import CloudflareSiteAdmin
9
+ from .log_admin import MaintenanceLogAdmin
10
+ from .scheduled_admin import ScheduledMaintenanceAdmin
13
11
 
14
12
  __all__ = [
15
- # Site management
13
+ 'CloudflareApiKeyAdmin',
16
14
  'CloudflareSiteAdmin',
17
- 'SiteGroupAdmin',
18
-
19
- # Maintenance tracking
20
- 'MaintenanceEventAdmin',
21
15
  'MaintenanceLogAdmin',
22
-
23
- # Monitoring
24
- 'MonitoringTargetAdmin',
25
-
26
- # Cloudflare integration
27
- 'CloudflareDeploymentAdmin',
16
+ 'ScheduledMaintenanceAdmin',
28
17
  ]